# Testing Methodologies Standards for TypeScript
This document outlines the standards for testing TypeScript code to ensure quality, reliability, and maintainability. It covers unit, integration, and end-to-end testing strategies, with a focus on modern approaches and patterns in the TypeScript ecosystem.
## 1. General Testing Principles
These principles apply to all types of testing and are essential for creating a robust and reliable codebase.
### 1.1. Write Tests That Are Independent and Repeatable
* **DO THIS:** Ensure that tests can be run in any order without affecting each other. Use mock data and isolated environments to avoid external dependencies.
* **DON'T DO THIS:** Rely on shared mutable state between tests. This leads to flaky tests that pass or fail unpredictably.
**WHY:** Independent tests provide consistent results, making debugging easier.
"""typescript
// DO THIS: Mock dependencies
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
import { User } from './user.model';
jest.mock('./user.repository');
describe('UserService', () => {
let userService: UserService;
let userRepositoryMock: jest.Mocked;
beforeEach(() => {
userRepositoryMock = {
getUserById: jest.fn(),
saveUser: jest.fn(),
} as jest.Mocked;
userService = new UserService(userRepositoryMock);
});
it('should get a user by ID', async () => {
const mockUser: User = { id: 1, name: 'Test User' };
userRepositoryMock.getUserById.mockResolvedValue(mockUser);
const user = await userService.getUserById(1);
expect(user).toEqual(mockUser);
expect(userRepositoryMock.getUserById).toHaveBeenCalledWith(1);
});
});
// DON'T DO THIS: Rely on database state
// Bad example: Test modifies a database record used by another test
"""
### 1.2. Write Tests That Are Fast
* **DO THIS:** Optimize tests to run quickly by avoiding unnecessary I/O operations or complex computations.
* **DON'T DO THIS:** Perform slow operations like initializing databases or making external API calls in unit tests. Use integration tests and end-to-end tests for these scenarios.
**WHY:** Fast tests encourage frequent test runs, reducing the time to catch and fix bugs.
"""typescript
// DO THIS: Use in-memory data structures for unit tests
import { calculateSum } from './math.utils';
describe('calculateSum', () => {
it('should calculate the sum of two numbers', () => {
expect(calculateSum(2, 3)).toBe(5);
});
});
// DON'T DO THIS: Complex setup in each test case
"""
### 1.3. Aim for High Code Coverage
* **DO THIS:** Strive to cover as much of your codebase as possible with tests, aiming for over 80% coverage. Use code coverage tools to identify gaps in your test suite.
* **DON'T DO THIS:** Equate high coverage with quality. Ensure tests assert the correct behavior and handle edge cases.
**WHY:** High code coverage reduces the risk of undetected bugs.
### 1.4. Use Meaningful Assertions
* **DO THIS:** Write assertions that clearly specify the expected outcome of the test. Use descriptive messages to make debugging easier.
* **DON'T DO THIS:** Use generic assertions that don't provide specific information about the failure.
**WHY:** Clear assertions help pinpoint the cause of a test failure quickly.
"""typescript
// DO THIS: Specific assertion
test('should return the correct greeting message', () => {
const result = greet('Alice');
expect(result).toBe('Hello, Alice!');
});
// DON'T DO THIS: Vague assertion
test('should return a string', () => {
const result = greet('Alice');
expect(typeof result).toBe('string'); // Not very specific
});
"""
### 1.5 Follow the Arrange-Act-Assert (AAA) Pattern
* **DO THIS:** Structure your tests to have a clear "Arrange", "Act", and "Assert" sections.
* **DON'T DO THIS:** Mix the stages, making the test hard to reason about.
**WHY:** Makes the test logic more understandable and easier to maintain.
"""typescript
describe('User Authentication', () => {
it('should authenticate a user with valid credentials', async () => {
// Arrange
const username = 'testuser';
const password = 'password123';
const userRepository = {
findUserByUsername: jest.fn().mockResolvedValue({ username, password }),
};
const authService = new AuthService(userRepository as any);
// Act
const result = await authService.authenticate(username, password);
// Assert
expect(result).toBe(true);
});
});
"""
## 2. Unit Testing
Unit tests focus on testing individual units (e.g., functions, classes) in isolation.
### 2.1. Use Mocking to Isolate Units
* **DO THIS:** Use mocking libraries like Jest or Sinon to simulate the behavior of dependencies.
* **DON'T DO THIS:** Directly use real dependencies in unit tests, as this couples the test to external systems.
**WHY:** Mocking simplifies the test setup and makes the test execution predictable.
"""typescript
// DO THIS: Mocking dependencies
import { EmailService } from './email.service';
import { sendWelcomeEmail } from './user.utils';
jest.mock('./email.service');
describe('sendWelcomeEmail', () => {
it('should send a welcome email to the user', async () => {
const mockEmailService = EmailService as jest.Mocked;
mockEmailService.sendEmail.mockResolvedValue(undefined);
await sendWelcomeEmail('test@example.com', 'Test User');
expect(mockEmailService.sendEmail).toHaveBeenCalledWith(
'test@example.com',
'Welcome',
'Welcome, Test User!'
);
});
});
"""
### 2.2. Test Boundary Conditions and Edge Cases
* **DO THIS:** Include tests that cover boundary conditions, such as empty inputs, maximum values, or invalid inputs.
* **DON'T DO THIS:** Only test the happy path. This increases the risk of bugs in production.
**WHY:** Testing edge cases ensures your code handles unexpected inputs gracefully.
"""typescript
// DO THIS: Test edge cases
import { divide } from './math.utils';
describe('divide', () => {
it('should divide two numbers correctly', () => {
expect(divide(10, 2)).toBe(5);
});
it('should throw an error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrowError('Cannot divide by zero');
});
it('should return 0 when dividing zero by a number', () => {
expect(divide(0, 5)).toBe(0);
});
});
"""
### 2.3. Leverage TypeScript's Type System in Tests
* **DO THIS:** Use TypeScript's type system to write type-safe tests. Define types for mock data and expected results.
* **DON'T DO THIS:** Use "any" or "unknown" excessively in tests, as this defeats the purpose of using TypeScript.
**WHY:** Type-safe tests catch type-related errors early.
"""typescript
// DO THIS: Use types in tests
import { User, createUser } from './user.model';
describe('createUser', () => {
it('should create a user with the given name and email', () => {
const userData: Omit = {
name: 'Test User',
email: 'test@example.com',
};
const user: User = createUser(userData);
expect(user.name).toBe(userData.name);
expect(user.email).toBe(userData.email);
expect(user.id).toBeDefined();
});
});
"""
### 2.4. Test Private Methods (Use with Caution)
* **DO THIS:** Avoid testing private methods directly, but if necessary, use techniques like accessing them through bracket notation or making them protected and creating a derived class for testing.
* **DON'T DO THIS:** Refactor code solely to make private methods testable. Consider the design implications.
**WHY:** Testing private methods can tightly couple tests to implementation details, making refactoring difficult, but sometimes testing them is crucial to ensure functionality.
"""typescript
// Testing private methods, a rare case where it's justified
class MyClass {
private add(a: number, b: number): number{
return a + b;
}
public performOperation(a: number, b:number): number {
return this.add(a,b) * 2;
}
}
describe('MyClass', () => {
it('should add two numbers correctly (testing private method)', () => {
const myClass = new MyClass();
// Accessing private method using bracket notation (use sparingly)
const result = (myClass as any).add(5, 3);
expect(result).toBe(8);
});
it('should perform the operation', ()=>{
const myClass = new MyClass();
const result = myClass.performOperation(5,3);
expect(result).toBe(16);
});
});
"""
## 3. Integration Testing
Integration tests verify the interaction between different parts of a system or application.
### 3.1. Choose Appropriate Scenarios
* **DO THIS:** Identify critical interactions between modules and write tests to verify these interactions.
* **DON'T DO THIS:** Attempt to test every possible interaction. Focus on essential scenarios that impact the system's overall behavior.
**WHY:** Integration tests ensure different parts of the system work together as expected.
### 3.2. Use Test Databases or Mocked External Services
* **DO THIS:** Set up a dedicated test database or use mocked external services to avoid affecting production data.
* **DON'T DO THIS:** Run integration tests against production databases. This can lead to data corruption or unintended side effects.
**WHY:** Isolated environments ensure predictable test results. Setting up a test database with seeding can be achieved using tools like Docker and custom scripts.
"""typescript
// Example showing how to setup an integration test using testcontainers
// Dockerfile (simplified)
// FROM postgres:latest
// ENV POSTGRES_USER=test
// ENV POSTGRES_PASSWORD=test
// ENV POSTGRES_DB=testdb
// Jest test
import { PostgreSqlContainer } from 'testcontainers';
import { DataSource } from 'typeorm';
describe('Database Integration Test', () => {
let container: PostgreSqlContainer;
let dataSource: DataSource;
beforeAll(async () => {
container = await new PostgreSqlContainer()
.withDatabase('testdb')
.withUsername('test')
.withPassword('test')
.start();
dataSource = new DataSource({
type: 'postgres',
host: container.getHost(),
port: container.getMappedPort(5432),
username: 'test',
password: 'test',
database: 'testdb',
entities: [], // Your entities
synchronize: true, // Auto-create schema for testing
});
await dataSource.initialize();
});
afterAll(async () => {
await dataSource.destroy();
await container.stop();
});
it('should be able to connect to the database', async () => {
expect(dataSource.isInitialized).toBe(true);
// Now you can perform actual database operations using dataSource
// And assert the expected result
});
});
"""
### 3.3. Verify Data Consistency
* **DO THIS:** Check that data is correctly persisted and retrieved from databases or external systems.
* **DON'T DO THIS:** Only verify the immediate result of an interaction. Ensure that the data remains consistent over time.
**WHY:** Data consistency is crucial for maintaining the integrity of the system.
"""typescript
// DO THIS: Verify data consistency
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
import { User } from './user.model';
describe('UserService Integration', () => {
let userService: UserService;
let userRepository: UserRepository;
beforeEach(() => {
userRepository = new UserRepository(); // Assuming it connects to a test DB
userService = new UserService(userRepository); //Real UserRepository, test DB.
});
it('should create and retrieve a user', async () => {
const userData: Omit = { name: 'Test User', email: 'test@example.com' };
const createdUser = await userService.createUser(userData);
const retrievedUser = await userService.getUserById(createdUser.id);
expect(retrievedUser).toEqual(createdUser); //Check they are the same
});
});
"""
### 3.4 Handling Asynchronous Operations in Integration Tests
* **DO THIS:** Use "async/await" or Promises to correctly handle asynchronous operations when testing integrations that involve databases, external APIs, or message queues.
* **DON'T DO THIS:** Forget to await asynchronous calls or fail to handle rejections, which can lead to incomplete tests or unhandled errors.
**WHY:** Prevents flaky and unreliable tests by correctly awaiting the completion of async tasks.
"""typescript
// Example: Integration test with asynchronous database retrieval
it('should retrieve a user asynchronously', async () => {
const userId = 123;
const mockUser = { id: userId, name: 'Async User' };
// Mock the userRepository's getUserById method to simulate async retrieval
userRepositoryMock.getUserById.mockResolvedValue(mockUser);
// Act: Call the service method that retrieves the user
const retrievedUser = await userService.getUserById(userId);
// Assert: Verify that the retrieved user matches the expected user
expect(retrievedUser).toEqual(mockUser);
expect(userRepositoryMock.getUserById).toHaveBeenCalledWith(userId);
});
"""
## 4. End-to-End (E2E) Testing
E2E tests simulate real user scenarios by testing the entire application from start to finish.
### 4.1. Use a Testing Framework
* **DO THIS:** Use a testing framework like Cypress, Playwright, or Puppeteer to automate browser interactions.
* **DON'T DO THIS:** Manually test the application. This is error-prone and time-consuming.
**WHY:** Automation reduces the cost of E2E testing and improves test coverage.
### 4.2. Focus on Key User Flows
* **DO THIS:** Identify critical user flows, such as login, registration, or checkout, and write E2E tests to verify they work correctly.
* **DON'T DO THIS:** Attempt to test every possible user interaction. Prioritize the most important flows.
**WHY:** Focusing on key flows ensures the application is usable for the majority of users.
### 4.3. Use Realistic Test Data
* **DO THIS:** Use realistic test data to simulate real-world scenarios. This includes user accounts, products, and orders.
* **DON'T DO THIS:** Use unrealistic or incomplete data, as this can lead to false positives or negatives.
**WHY:** Realistic data ensures that tests accurately reflect the application's behavior in production. Consider using a seeding strategy to populate test data.
### 4.4. Clean Up After Tests
* **DO THIS:** Clean up any data created during the test execution, such as deleting user accounts or resetting the database.
* **DON'T DO THIS:** Leave the application in an inconsistent state after the tests have finished.
**WHY:** Cleaning up ensures that subsequent tests are not affected by previous runs.
"""typescript
// Example Cypress test (Cypress uses JavaScript/TypeScript)
describe('User Registration', () => {
it('should register a new user successfully', () => {
cy.visit('/register');
cy.get('#name').type('Test User');
cy.get('#email').type('test@example.com');
cy.get('#password').type('password123');
cy.get('#confirmPassword').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome, Test User!').should('be.visible');
});
afterEach(() => {
// Clean up by deleting the user account
cy.request('DELETE', '/api/users/test@example.com'); //Assuming a DELETE API
});
});
"""
## 5. Testing Tools and Libraries
* **Jest:** A popular testing framework with built-in mocking and assertion capabilities.
* **Mocha:** A flexible testing framework that can be combined with assertion libraries like Chai and mocking libraries like Sinon.
* **Cypress:** An E2E testing framework specifically designed for web applications.
* **Playwright:** A framework for reliable end-to-end testing for web apps. Supports Chromium, Firefox and Webkit
* **Puppeteer:** A Node library that provides a high-level API to control Chrome or Chromium.
* **Testcontainers:** A library that provides lightweight, throwaway instances of databases, message brokers, and more. Ideal for integration testing.
* **Supertest:** A library for testing HTTP APIs.
## 6. Continuous Integration (CI)
* **DO THIS:** Integrate your tests into a CI/CD pipeline to automatically run tests on every code change.
* **DON'T DO THIS:** Rely on manual test runs.
* **WHY:** Automating tests catches bugs early and prevents them from reaching production.
## 7. Testing React specific components with TypeScript
Testing React components that are written in TypeScript introduces a few nuances.
### 7.1 Use Testing Library
* **DO THIS**: Use React Testing Library instead of Enzyme. React Testing Library encourages testing components from a user's perspective.
* **DON'T DO THIS**: Write tests that rely on implementation details.
"""typescript
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('should display the correct message', () => {
render();
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
});
it('should call the onClick handler when the button is clicked', async () => {
const handleClick = jest.fn();
render();
await userEvent.click(screen.getByText('Click Me!'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
// DON'T DO THIS - Testing implementation details.
// This test will break if you change the className of the element.
it('should have the correct class name', () => {
render();
expect(screen.getByText('Class Name Test')).toHaveClass('my-component');
});
"""
### 7.2 Mocking Modules (Properly)
* **DO THIS**: When your React component imports external modules, mock them appropriately.
* **DON'T DO THIS**: Directly import and use real modules in your tests, resulting in integration tests rather than isolated unit tests.
"""typescript
jest.mock('./api', () => ({ // Mock the entire module
fetchData: jest.fn(() => Promise.resolve({ data: 'mocked data' })), }));
it('fetches data correctly', async () => { const { findByText } = render(); expect(await findByText('mocked data')).toBeInTheDocument();});
"""
### 7.3 Testing Component State Changes
* **DO THIS:** Test state changes thoroughly, including initial state, state updates based on user interactions, and state-dependent rendering.
* **DON'T DO THIS:** Assume state updates correctly without verifying the changes in the rendered output.
"""typescript
import React, { useState } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
function Counter() {
const [count, setCount] = useState(0);
return (
<p>Count: {count}</p>
setCount(count + 1)}>Increment
);
}
test('increments the count when the button is clicked', () => {
render();
const button = screen.getByText('Increment');
const countDisplay = screen.getByText('Count: 0');
fireEvent.click(button);
expect(countDisplay).toHaveTextContent('Count: 1');
fireEvent.click(button);
expect(countDisplay).toHaveTextContent('Count: 2'); // Verify multiple increments
});
"""
This comprehensive document provides a solid foundation for establishing effective testing methodologies in TypeScript projects, ensuring code quality, reliability, and maintainability. Remember that this is a living document that should be updated as the TypeScript ecosystem evolves and new best practices emerge.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# TypeScript Performance Optimization Standards: Best Practices for Efficient Applications This document outlines coding standards and best practices specifically for performance optimization in TypeScript projects. Adhering to these guidelines will improve the speed, responsiveness, efficient use of resources, and overall user experience of your applications. ## Table of Contents - [1. Architectural Considerations for Performance](#1-architectural-considerations-for-performance) - [1.1. Code Splitting](#11-code-splitting) - [1.2. Lazy Loading Modules](#12-lazy-loading-modules) - [1.3. Server-Side Rendering (SSR) or Static Site Generation (SSG)](#13-server-side-rendering-ssr-or-static-site-generation-ssg) - [1.4. Data Structure Selection](#14-data-structure-selection) ## 1. Architectural Considerations for Performance ### 1.1. Code Splitting **Standard:** Implement code splitting to reduce the initial load time of your application. **Why:** Loading only the necessary code on initial page load significantly improves the user experience. **Do This:** * Utilize dynamic imports (`import()`) to load modules on demand. * Configure your bundler (Webpack, Parcel, Rollup) to create separate chunks for different parts of your application. **Don't Do This:** * Load the entire application code in a single bundle. * Use `require()` statements (CommonJS) in modern TypeScript projects where ES Modules are supported. **Example:** ```typescript // Before: Loading everything upfront import { featureA } from './featureA'; import { featureB } from './featureB'; // After: Code splitting with dynamic imports async function loadFeatureA() { const { featureA } = await import('./featureA'); featureA.init(); } async function loadFeatureB() { const { featureB } = await import('./featureB'); featureB.init(); } // Use loadFeatureA or loadFeatureB based on user interaction or route ``` **Bundler Configuration (Webpack example):** ```javascript // webpack.config.js module.exports = { entry: './src/index.ts', output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, optimization: { splitChunks: { chunks: 'all', // Split all chunks of code }, }, }; ``` ### 1.2. Lazy Loading Modules **Standard:** Employ lazy loading for non-critical modules or components. **Why:** Reduce the amount of code that needs to be parsed and compiled on initial load. **Do This:** * Load components or modules only when they are needed. * Utilize Intersection Observer API to load components when they become visible in the viewport. **Don't Do This:** * Load modules that are not immediately required for the current user interaction. **Example (Intersection Observer Lazy Loading):** ```typescript function lazyLoadComponent(element: HTMLElement, importPath: string) { const observer = new IntersectionObserver((entries) => { entries.forEach(async (entry) => { if (entry.isIntersecting) { const { default: Component } = await import(importPath); const componentInstance = new Component(); // Instantiate the component. element.appendChild(componentInstance.render()); // Append to the DOM (adjust according to your framework). observer.unobserve(element); } }); }); observer.observe(element); } // Usage: const lazyComponentElement = document.getElementById('lazy-component'); if (lazyComponentElement) { lazyLoadComponent(lazyComponentElement, './MyHeavyComponent'); } ``` ### 1.3. Server-Side Rendering (SSR) or Static Site Generation (SSG) **Standard:** Consider using SSR or SSG for content-heavy, SEO-sensitive, or performance-critical applications. **Why:** Reduces the time to first paint (TTFP) and improves SEO by providing crawlers with pre-rendered content. **Do This:** * Evaluate the trade-offs between SSR, SSG, and client-side rendering (CSR) based on your application's needs. * Use frameworks like Next.js (React), Nuxt.js (Vue), or Angular Universal. * Implement appropriate caching strategies for SSR. **Don't Do This:** * Default to CSR when SSR or SSG could provide significant performance benefits. **Example (Next.js):** ```typescript // pages/index.tsx (Next.js example) import React from 'react'; interface Props { data: { title: string; description: string; }; } const HomePage: React.FC<Props> = ({ data }) => { return ( <div> <h1>{data.title}</h1> <p>{data.description}</p> </div> ); }; export async function getServerSideProps() { // Fetch data from an API, database, or file system. const data = { title: 'My Awesome Website', description: 'Welcome to my extremely performant website!', }; return { props: { data, }, }; } export default HomePage; ``` ### 1.4. Data Structure Selection **Standard:** Select the most appropriate data structure for each specific use case. **Why:** Using appropriate data structures will reduce the complexity and improve the execution speed of algorithms. **Do This:** * Use `Map` when you need to associate keys with values, especially when the keys are not strings or numbers. * Use `Set` when you need to store a collection of unique values. * Use `Record<K, V>` type for type-safe object mapping. * Consider specialized data structures for specific performance needs (e.g., priority queues, linked lists). **Don't Do This:** * Use generic arrays or objects when more specialized data structures would be more efficient. * Perform frequent lookups in arrays when using a Map or Set would be more performant. **Example:** ```typescript // Before: Using an array for lookups const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]; // O(n) lookup operation const findUser = (id: number) => users.find(user => user.id === id); // After: Using Map for efficient lookups const userMap = new Map<number, {id: number, name: string}>(); userMap.set(1, { id: 1, name: 'Alice' }); userMap.set(2, { id: 2, name: 'Bob' }); userMap.set(3, { id: 3, name: 'Charlie' }); // O(1) lookup operation const getUser = (id: number) => userMap.get(id); ```
Debe preferir usar el gestor de versiones "pnpm" por sobre "npm" para la ejecución de los comandos.
# Storybook Guidelines for Angular (v9.0.18+) Use these guidelines when working with Storybook in Angular projects. This document covers modern patterns, best practices, and the latest features in Storybook 9.x. ## 1. Core Architecture & Setup ### Framework Configuration - **Angular Framework**: Always use `@storybook/angular` as the framework in your `.storybook/main.ts` - **TypeScript First**: All configuration files should use TypeScript (`.ts` extension) - **Standalone Components**: Leverage Angular standalone components in stories - they work seamlessly with Storybook - **Modern Angular Patterns**: Support for Angular 17+ features including signals, control flow, and standalone APIs ### Basic Configuration Structure ```typescript // .storybook/main.ts import type { StorybookConfig } from '@storybook/angular'; const config: StorybookConfig = { framework: { name: '@storybook/angular', options: { // Framework-specific options }, }, stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ '@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/addon-a11y', '@storybook/addon-docs', '@storybook/addon-vitest', // For Storybook 9.x testing ], }; export default config; ``` ## 2. Story Structure & CSF 3 Patterns ### Modern Story Format (CSF 3) - **Component Story Format 3**: Use the latest CSF 3 syntax for all new stories - **TypeScript Types**: Always use `Meta` and `StoryObj` for type safety - **Minimal Boilerplate**: Leverage CSF 3's simplified syntax ```typescript import type { Meta, StoryObj } from '@storybook/angular'; import { Button } from './button.component'; const meta: Meta = { component: Button, }; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; export const Secondary: Story = { args: { primary: false, label: 'Button', }, }; ``` ### Story Naming & Organization - **Descriptive Names**: Use clear, descriptive names for story exports - **Hierarchical Titles**: Organize stories with meaningful hierarchies using forward slashes - **Auto-Generated Titles**: Leverage automatic title generation when possible - **Component-Centric**: Group stories by component, not by state ```typescript const meta: Meta = { title: 'Design System/Components/Button', // Hierarchical organization component: Button, }; ``` ## 3. Angular-Specific Patterns ### Standalone Components (Recommended) - **Default Approach**: Prefer standalone components for new development - **No Module Imports**: Avoid importing CommonModule or other NgModules - **Direct Dependencies**: Import only required standalone components, directives, or pipes ```typescript // ✅ Good - Standalone component story import type { Meta, StoryObj } from '@storybook/angular'; import { MyStandaloneComponent } from './my-standalone.component'; const meta: Meta = { component: MyStandaloneComponent, }; ``` ### Legacy Module-Based Components - **Module Metadata**: Use `moduleMetadata` decorator for components requiring NgModules - **Minimal Imports**: Import only necessary modules and declarations ```typescript // For legacy components requiring modules import { moduleMetadata } from '@storybook/angular'; import { CommonModule } from '@angular/common'; const meta: Meta = { component: LegacyComponent, decorators: [ moduleMetadata({ imports: [CommonModule], declarations: [LegacyComponent, ChildComponent], }), ], }; ``` ### Application Configuration - **Provider Setup**: Use `applicationConfig` decorator for dependency injection - **Service Mocking**: Configure providers for testing scenarios ```typescript import { applicationConfig } from '@storybook/angular'; import { importProvidersFrom } from '@angular/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; const meta: Meta = { component: MyComponent, decorators: [ applicationConfig({ providers: [ importProvidersFrom(BrowserAnimationsModule), // Add other providers as needed ], }), ], }; ``` ## 4. Story Configuration & Best Practices ### Args and Controls - **Typed Args**: Leverage TypeScript for type-safe arguments - **Meaningful Defaults**: Provide sensible default values - **Control Types**: Explicitly define control types when needed ```typescript const meta: Meta = { component: Button, argTypes: { size: { control: { type: 'select' }, options: ['small', 'medium', 'large'], }, disabled: { control: { type: 'boolean' }, }, }, args: { // Default args for all stories size: 'medium', disabled: false, label: 'Button', }, }; ``` ### Parameters Configuration - **Global Parameters**: Set common parameters at the meta level - **Story-Specific Overrides**: Override parameters for specific stories when needed - **Documentation**: Use parameters for docs configuration ```typescript const meta: Meta = { component: Button, parameters: { docs: { description: { component: 'A versatile button component for user interactions.', }, }, backgrounds: { default: 'light', }, }, }; export const OnDark: Story = { parameters: { backgrounds: { default: 'dark' }, }, }; ``` ## 5. Advanced Patterns ### Custom Render Functions - **Complex Templates**: Use render functions for complex component templates - **Template Customization**: Provide custom templates when component alone isn't sufficient ```typescript export const WithCustomTemplate: Story = { render: args => ({ props: args, template: ` <p>Additional content around the button</p> `, }), }; ``` ### Component Composition - **Multi-Component Stories**: Show components working together - **Real-World Scenarios**: Create stories that demonstrate actual usage patterns ```typescript const meta: Meta = { component: List, decorators: [ moduleMetadata({ declarations: [List, ListItem], }), ], }; export const WithItems: Story = { render: args => ({ props: args, template: ` Item 1 Item 2 Item 3 `, }), }; ``` ## 6. Testing Integration (Storybook 9.x) ### Vitest Addon Integration - **Modern Testing**: Use `@storybook/addon-vitest` for component testing - **Story-Based Tests**: Transform stories into tests automatically - **Browser Mode**: Leverage Vitest's browser mode for realistic testing ```bash # Install and configure Vitest addon npx storybook@latest add @storybook/addon-vitest ``` ### Interaction Testing - **Play Functions**: Use play functions for interaction testing - **User Events**: Simulate real user interactions - **Assertions**: Include meaningful assertions in play functions ```typescript import { userEvent, within, expect } from '@storybook/test'; export const InteractiveTest: Story = { args: { label: 'Click me', }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); const button = canvas.getByRole('button'); await userEvent.click(button); await expect(button).toHaveClass('clicked'); }, }; ``` ### Accessibility Testing - **A11y Addon**: Include `@storybook/addon-a11y` for accessibility checks - **ARIA Labels**: Ensure proper ARIA labeling in stories - **Keyboard Navigation**: Test keyboard accessibility ```typescript const meta: Meta = { component: Button, parameters: { a11y: { config: { rules: [ { id: 'color-contrast', enabled: true, }, ], }, }, }, }; ``` ## 7. Documentation & Docs Integration ### Compodoc Integration - **API Documentation**: Integrate Compodoc for automatic API docs generation - **Angular.json Configuration**: Configure builders for Compodoc integration ```json // angular.json { "projects": { "your-project": { "architect": { "storybook": { "builder": "@storybook/angular:start-storybook", "options": { "compodoc": true, "compodocArgs": ["-e", "json", "-d", "."] } } } } } } ``` ```typescript // .storybook/preview.ts import { setCompodocJson } from '@storybook/addon-docs/angular'; import docJson from '../documentation.json'; setCompodocJson(docJson); ``` ### Story Documentation - **JSDoc Comments**: Use JSDoc comments for automatic documentation - **Description Parameters**: Override descriptions when needed - **Code Examples**: Include relevant code examples ```typescript /** * Primary button component for user actions. * Supports various sizes and states. */ const meta: Meta = { component: Button, parameters: { docs: { description: { component: 'The primary button component with full accessibility support.', }, }, }, }; /** * The primary button state - used for main actions */ export const Primary: Story = { parameters: { docs: { description: { story: 'Use this variant for primary actions like "Save" or "Submit".', }, }, }, }; ``` ## 8. Performance & Optimization ### Lazy Loading - **Code Splitting**: Leverage Angular's lazy loading for large component libraries - **Story Optimization**: Keep stories focused and lightweight - **Asset Management**: Optimize images and other assets ### Bundle Optimization - **Tree Shaking**: Ensure proper tree shaking of unused code - **Minimal Dependencies**: Import only necessary dependencies - **Build Configuration**: Optimize build configuration for production ## 9. Theming & Styling ### Angular Material Integration - **Theme Providers**: Use decorators to provide Angular Material themes - **Component Wrapper**: Wrap stories with theme providers when needed ```typescript import { componentWrapperDecorator } from '@storybook/angular'; const meta: Meta = { component: MyComponent, decorators: [componentWrapperDecorator(story => `${story}`)], }; ``` ### CSS Custom Properties - **Design Tokens**: Use CSS custom properties for consistent theming - **Theme Switching**: Implement theme switching in stories - **Responsive Design**: Test components across different viewports ## 10. File Organization & Naming ### File Structure - **Co-location**: Keep stories close to their components - **Consistent Naming**: Use consistent naming patterns - **Logical Grouping**: Group related stories together ``` src/ components/ button/ button.component.ts button.component.html button.component.scss button.component.stories.ts button.component.spec.ts ``` ### Naming Conventions - **Story Files**: Use `.stories.ts` extension - **Export Names**: Use PascalCase for story exports - **File Names**: Use kebab-case for file names ## 11. CI/CD Integration ### Build Configuration - **Static Builds**: Configure static build for deployment - **Environment Variables**: Handle environment-specific configuration - **Testing Pipeline**: Integrate story testing in CI/CD ```bash # Build Storybook for production ng run your-project:build-storybook # Run tests with Vitest addon npm run test-storybook ``` ### Deployment - **Static Hosting**: Deploy to static hosting services - **Version Management**: Tag releases with version numbers - **Documentation Updates**: Keep documentation in sync with code ## 12. Migration & Maintenance ### Upgrading Storybook - **Regular Updates**: Keep Storybook updated to latest versions - **Migration Guides**: Follow official migration guides - **Breaking Changes**: Test thoroughly after major updates ### Legacy Code Migration - **Gradual Migration**: Migrate stories incrementally - **CSF 2 to CSF 3**: Upgrade older story formats - **Module to Standalone**: Migrate to standalone components when possible ## 13. Common Patterns & Examples ### Form Components ```typescript export const FormExample: Story = { render: args => ({ props: args, template: ` `, }), }; ``` ### Data Loading States ```typescript export const Loading: Story = { args: { loading: true, data: null, }, }; export const WithData: Story = { args: { loading: false, data: mockData, }, }; export const Error: Story = { args: { loading: false, error: 'Failed to load data', }, }; ``` ### Responsive Components ```typescript export const Mobile: Story = { parameters: { viewport: { defaultViewport: 'mobile1', }, }, }; export const Desktop: Story = { parameters: { viewport: { defaultViewport: 'desktop', }, }, }; ``` ## 14. Quality Guidelines ### Code Quality - **TypeScript Strict Mode**: Use strict TypeScript configuration - **ESLint Rules**: Follow Storybook-specific ESLint rules - **Consistent Formatting**: Use Prettier for code formatting ### Testing Standards - **Coverage Goals**: Aim for high story coverage of component states - **Interaction Tests**: Include interaction tests for complex components - **Accessibility Tests**: Ensure all stories pass accessibility checks ### Documentation Standards - **Complete Coverage**: Document all component props and behaviors - **Real Examples**: Provide realistic usage examples - **Up-to-date**: Keep documentation synchronized with code changes ## 15. Troubleshooting & Common Issues ### Angular-Specific Issues - **Module Dependencies**: Ensure all required modules are imported - **Provider Configuration**: Check provider setup for dependency injection - **Change Detection**: Consider OnPush change detection strategy ### Performance Issues - **Bundle Size**: Monitor and optimize bundle size - **Memory Leaks**: Watch for memory leaks in complex stories - **Build Time**: Optimize build configuration for faster development This comprehensive guide ensures you're following the latest best practices for Storybook 9.x with Angular, leveraging modern features like the Vitest addon, improved testing capabilities, and optimized development workflows.
# Code Style and Conventions Standards for TypeScript This document outlines coding style and conventions standards for TypeScript development. Adhering to these standards promotes code consistency, readability, maintainability, and collaboration within development teams. These guidelines are tailored for the latest version of TypeScript and aim to leverage modern best practices. ## 1. Formatting Consistent formatting is crucial for readability. We adopt the following standards for TypeScript code formatting: ### 1.1. Whitespace and Indentation * **Standard:** Use 2 spaces for indentation. Avoid tabs. * **Why:** Consistent indentation enhances readability and reduces visual clutter. * **Do This:** """typescript function calculateArea(width: number, height: number): number { const area = width * height; return area; } """ * **Don't Do This:** """typescript function calculateArea(width: number, height: number): number { const area = width * height; return area; } """ * **Standard:** Use blank lines to separate logical sections of code within functions and classes. * **Why:** Separating logical blocks improves code comprehension. * **Do This:** """typescript function processData(data: any[]): void { // Validate data if (!data || data.length === 0) { throw new Error("Data is invalid."); } // Transform data const transformedData = data.map(item => ({ ...item, processed: true, })); // Save data saveToDatabase(transformedData); } """ ### 1.2. Line Length * **Standard:** Limit lines to a maximum of 120 characters. * **Why:** Enforces readability on various screen sizes and IDE configurations. * **How:** Configure your editor or IDE to display a line length guide at 120 characters. Break long lines at logical points, such as after commas, operators, or before opening parentheses. * **Do This:** """typescript const veryLongVariableName = calculateSomethingComplicated( param1, param2, param3 ); """ * **Don't Do This:** """typescript const veryLongVariableName = calculateSomethingComplicated(param1, param2, param3); """ ### 1.3. Braces and Parentheses * **Standard:** Use braces for all control flow statements, even single-line statements. * **Why:** Improves code clarity and reduces potential errors when modifying code. * **Do This:** """typescript if (isValid) { console.log("Valid"); } else { console.log("Invalid"); } """ * **Don't Do This:** """typescript if (isValid) console.log("Valid"); else console.log("Invalid"); """ * **Standard:** Use parentheses to clarify operator precedence, where needed. * **Why:** Reduces ambiguity, especially in complex expressions. * **Example:** """typescript const result = (a + b) * c; """ ### 1.4. Semicolons * **Standard:** Always use semicolons to terminate statements. * **Why:** Prevents unexpected behavior due to JavaScript's automatic semicolon insertion (ASI). * **Do This:** """typescript const name = "John"; console.log(name); """ * **Don't Do This:** """typescript const name = "John" console.log(name) """ ## 2. Naming Conventions Consistent naming conventions are essential for code clarity and maintainability. ### 2.1. Variables and Constants * **Standard:** Use camelCase for variable and constant names. * **Why:** Widely adopted convention for JavaScript and TypeScript. * **Do This:** """typescript const userName = "Alice"; let itemCount = 0; """ * **Standard:** Use UPPER_SNAKE_CASE for constant values (i.e. values that may be inlined for performance or are known at compile time). * **Why:** Clearly distinguishes constants from variables. * **Do This:** """typescript const MAX_RETRIES = 3; const API_ENDPOINT = "https://example.com/api"; """ ### 2.2. Functions and Methods * **Standard:** Use camelCase for function and method names. * **Why:** Follows common JavaScript/TypeScript conventions. * **Do This:** """typescript function calculateTotal(price: number, quantity: number): number { return price * quantity; } class ShoppingCart { addItem(item: string): void { console.log("Adding ${item} to the cart."); } } """ ### 2.3. Classes and Interfaces * **Standard:** Use PascalCase for class and interface names. * **Why:** Clearly identifies classes and interfaces. * **Do This:** """typescript interface User { id: number; name: string; } class Product { constructor(public name: string, public price: number) {} } """ ### 2.4. Type Parameters * **Standard:** Use single uppercase letters, typically "T", "U", "V", etc., for generic type parameters. * **Why:** Follows established TypeScript conventions. * **Do This:** """typescript function identity<T>(arg: T): T { return arg; } """ ### 2.5. Boolean Variables * **Standard:** Prefix boolean variables with "is", "has", or "should" to indicate a boolean value. * **Why:** Improves readability by clearly indicating the purpose of the variable. * **Do This:** """typescript let isValid: boolean = true; let hasPermission: boolean = false; let shouldUpdate: boolean = true; """ ## 3. Stylistic Consistency Consistency in style is critical for code maintainability. ### 3.1. Type Annotations and Inference * **Standard:** Use explicit type annotations where type inference is not obvious, especially for function parameters and return types. * **Why:** Improves code clarity and helps catch type-related errors early. * **Do This:** """typescript function greet(name: string): string { return "Hello, ${name}!"; } const add: (x: number, y: number) => number = (x, y) => x + y; """ * **Don't Do This:** """typescript function greet(name) { // Implicit 'any' type return "Hello, ${name}!"; } """ * **Standard:** Leverage type inference for local variables when the type is immediately apparent. * **Why:** Reduces verbosity and keeps code concise. * **Do This:** """typescript const message = "Hello, world!"; // Type inferred as string const count = 10; // Type inferred as number """ * **Standard:** When initializing variables with "null" or "undefined", explicitly define the type. * **Why:** Helps avoid unexpected type-related issues later. * **Do This:** """typescript let user: User | null = null; let data: string[] | undefined = undefined; """ ### 3.2. String Usage * **Standard:** Prefer template literals for string concatenation and multi-line strings. * **Why:** More readable and easier to maintain compared to traditional string concatenation. * **Do This:** """typescript const name = "Alice"; const message = "Hello, ${name}!"; const multiLine = "This is a multi-line string."; """ * **Don't Do This:** """typescript const name = "Alice"; const message = "Hello, " + name + "!"; const multiLine = "This is a\n" + "multi-line string."; """ ### 3.3. Object Literals * **Standard:** Use shorthand notation for object properties when the property name matches the variable name. * **Why:** Improves code conciseness and readability. * **Do This:** """typescript const name = "Alice"; const age = 30; const user = { name, age }; // Shorthand notation """ * **Don't Do This:** """typescript const name = "Alice"; const age = 30; const user = { name: name, age: age }; """ * **Standard:** Use object spread syntax for creating copies of objects or merging objects. * **Why:** More concise and readable than older methods like "Object.assign()". * **Do This:** """typescript const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // Creates a new object with a, b, and c """ ### 3.4. Arrow Functions * **Standard:** Use arrow functions for concise function expressions, especially for callbacks and inline functions. * **Why:** More compact syntax and lexically binds "this". * **Do This:** """typescript const numbers = [1, 2, 3]; const squared = numbers.map(x => x * x); // Concise arrow function """ * **Don't Do This:** """typescript const numbers = [1, 2, 3]; const squared = numbers.map(function(x) { return x * x; }); """ * **Standard:** Omit parentheses for single-parameter arrow functions. * **Why:** Makes the code even more concise. * **Do This:** """typescript const increment = x => x + 1; """ * **Don't Do This:** """typescript const increment = (x) => x + 1; """ ### 3.5. Modern TypeScript Features * **Standard:** Leverage features like optional chaining ("?.") and nullish coalescing ("??") for safer and more concise code. Optional properties on interfaces may be relevant if these are in use. * **Why:** Reduces boilerplate and improves null/undefined handling. * **Do This:** """typescript interface User { profile?: { address?: { city?: string; } } } const user: User = {}; const city = user?.profile?.address?.city ?? "Unknown"; // Nullish coalescing console.log(city); interface Config { timeout?: number; } const defaultConfig: Config = { timeout: 5000, }; function initialize(config?: Config) { const timeout = config?.timeout ?? defaultConfig.timeout; console.log("Timeout: ${timeout}"); } initialize(); // Timeout: 5000 initialize({ timeout: 10000 }); // Timeout: 10000 """ * **Standard:** Utilize discriminated unions and exhaustive checks for increased type safety and maintainability. * **Why:** Improves type correctness and makes it easier to handle different states or object types. * **Do This:** """typescript interface Success { type: "success"; result: any; } interface Error { type: "error"; message: string; } type Result = Success | Error; function handleResult(result: Result) { switch (result.type) { case "success": console.log("Success:", result.result); break; case "error": console.error("Error:", result.message); break; default: // Exhaustive check: TypeScript will flag this if a new type is added to Result const _exhaustiveCheck: never = result; return _exhaustiveCheck; } } const successResult: Success = { type: "success", result: { data: "example" } }; const errorResult: Error = { type: "error", message: "Something went wrong" }; handleResult(successResult); handleResult(errorResult); """ ### 3.6. Asynchronous Code * **Standard:** Always use "async/await" syntax for asynchronous operations. * **Why:** Improves readability and simplifies error handling compared to traditional promise chains. * **Do This:** """typescript async function fetchData(): Promise<any> { try { const response = await fetch("https://example.com/api/data"); const data = await response.json(); return data; } catch (error) { console.error("Error fetching data:", error); throw error; } } """ * **Don't Do This:** """typescript function fetchData(): Promise<any> { return fetch("https://example.com/api/data") .then(response => response.json()) .then(data => data) .catch(error => { console.error("Error fetching data:", error); throw error; }); } """ * **Standard:** Use "Promise.all" for concurrent asynchronous operations that don't depend on each other. * **Why:** Improves performance by executing asynchronous tasks in parallel. * **Do This:** """typescript async function processData(): Promise<void> { const [result1, result2] = await Promise.all([ fetchData1(), fetchData2(), ]); console.log("Result 1:", result1); console.log("Result 2:", result2); } """ ### 3.7 Error Handling * **Standard:** Implement robust error handling using "try...catch" blocks, especially in asynchronous functions. * **Why:** Prevents unhandled exceptions and allows for graceful recovery. * **Do This:** """typescript async function doSomething() { try { const result = await someAsyncOperation(); console.log("Result:", result); } catch (error) { console.error("An error occurred:", error); // Implement specific error handling/logging } } """ * **Standard:** Create custom error classes to provide more context and specify error handling logic. * **Why:** Extends the built-in Error to communicate more specific information about the error to the outside world. * **Do This:** """typescript class CustomError extends Error { constructor(message: string, public errorCode: number) { super(message); this.name = "CustomError"; Object.setPrototypeOf(this, CustomError.prototype); } } async function performOperation() { try { // some operation throw new CustomError("Operation failed", 500); } catch (error) { if (error instanceof CustomError) { console.error("Custom Error ${error.errorCode}: ${error.message}"); } else { console.error("An unexpected error occurred:", error); } } } """ ### 3.8 Immutability * **Standard:** Strive for immutability whenever practical, using "const" for variables that should not be reassigned and avoiding direct modification of objects and arrays. * **Why:** Makes code more predictable and easier to reason about, reducing the risk of bugs. * **Do This:** """typescript const originalArray = [1, 2, 3]; const newArray = [...originalArray, 4]; // Creates a new array const originalObject = { a: 1, b: 2 }; const newObject = { ...originalObject, c: 3 }; // Creates a new object """ * **Don't Do This:** """typescript const originalArray = [1, 2, 3]; originalArray.push(4); // Modifies the original array const originalObject = { a: 1, b: 2 }; originalObject.c = 3; // Modifies the original object """ ### 3.9 Comments * **Standard:** Add comments to explain complex or non-obvious logic, but prioritize writing self-documenting code. * **Why:** Comments should supplement, not replace, clear code. * **Guidelines:** * Use JSDoc-style comments for documenting functions, classes, and interfaces. * Explain the *why*, not the *what*. The code should explain what it does. * Keep comments concise and up-to-date. * Remove outdated or redundant comments. * **Example:** """typescript /** * Calculates the area of a rectangle. * @param width The width of the rectangle. * @param height The height of the rectangle. * @returns The area of the rectangle. */ function calculateArea(width: number, height: number): number { return width * height; } """ ## 4. Technology Specific Details (Distinguishing Good from Great) * **Standard:** Take advantage of TypeScript's advanced type features to create robust and maintainable code structures. * **Guidelines:** * **Utility Types:** Employ TypeScript's utility types ("Partial", "Readonly", "Pick", "Omit", "Record", etc.) to manipulate types and generate new types efficiently. """typescript interface User { id: number; name: string; email: string; age: number; } // Make all properties optional type PartialUser = Partial<User>; // Make specified properties required type RequiredIdAndName = Required<Pick<User, 'id' | 'name'>>; // Type with only certain properties type UserInfo = Pick<User, 'name' | 'email'>; // Type without certain properties type UserWithoutId = Omit<User, 'id'>; """ * **Mapped Types:** Utilize mapped types to transform the properties of an existing type, providing a dynamic way to define new types based on existing ones. """typescript interface Product { id: string; name: string; price: number; } // Create a read-only version of Product type ReadonlyProduct = Readonly<Product>; // Create a type where all properties of Product are nullable type NullableProduct = { [K in keyof Product]: Product[K] | null; }; """ * **Conditional Types:** Conditional types allow to define types based on conditions, adding yet another powerful layer of abstraction to type definitions. They help to ensure type safety throughout an application. """typescript type NonNullableProperty<T, K extends keyof T> = T[K] extends null | undefined ? never : K; type RequiredProperties<T> = Pick<T, NonNullableProperty<T, keyof T>>; interface Configuration { host?: string; port?: number; // Port can be potentially undefined or null timeout?: number; } // Extracts properties that are guaranteed to be assigned during runtime. type RuntimeProperties = RequiredProperties<Configuration>; // Result equivalent to {timeout: number;} """ * **Decorators:** Use decorators to add metadata or modify the behavior of classes, methods, properties, or parameters. * **Why:** Provide a declarative and reusable way to add functionality, such as logging, validation, or dependency injection. * **Example:** """typescript function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log("Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}"); const result = originalMethod.apply(this, args); console.log("Method ${propertyKey} returned: ${result}"); return result; }; return descriptor; } class Calculator { @logMethod add(a: number, b: number): number { return a + b; } } const calculator = new Calculator(); calculator.add(2, 3); // Output: // Calling method add with arguments: [2,3] // Method add returned: 5 """ ## 5. Tooling * **Standard:** Use Prettier for automatic code formatting and ESLint with recommended TypeScript rules for linting. * **Why:** Automates formatting and enforces code quality, reducing manual effort and improving consistency. * **Configuration:** Configure Prettier and ESLint to work seamlessly with your IDE and CI/CD pipeline. * **Example ".eslintrc.js" configuration:** """javascript module.exports = { parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: 2020, sourceType: "module", project: './tsconfig.json', }, plugins: ["@typescript-eslint"], extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", "prettier", ], rules: { // Add or override rules here }, }; """ By adhering to these coding standards, TypeScript projects will benefit from improved code quality, readability, and maintainability, fostering a collaborative and productive development environment.
# Core Architecture Standards for TypeScript This document outlines coding standards specifically for the core architecture of TypeScript projects. It focuses on fundamental architectural patterns, project structure, and organization principles essential for large, maintainable, and scalable TypeScript applications. ## 1. Architectural Patterns ### 1.1 Microservices Architecture **Standard:** When building large-scale applications, consider a microservices architecture. * **Do This:** Decompose the application into independent, deployable services. Each service should own a specific business domain. * **Don't Do This:** Create a monolithic application that combines disparate functionalities into a single codebase, leading to tightly coupled components and scalability bottlenecks. **Why:** Microservices enable independent scaling, deployment, and technology choices for different parts of the application, significantly boosting agility and resilience. **Code Example:** Illustrating a simple microservice interface """typescript // microservice-interface.ts export interface UserService { getUser(id: string): Promise<{ id: string; name: string; email: string }>; createUser(user: { name: string; email: string }): Promise<string>; } export class UserServiceClient implements UserService { private baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; } async getUser(id: string): Promise<{ id: string; name: string; email: string }> { const response = await fetch("${this.baseUrl}/users/${id}"); return response.json(); } async createUser(user: { name: string; email: string }): Promise<string> { const response = await fetch("${this.baseUrl}/users", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(user), }); const newUser = await response.json(); return newUser.id; } } """ ### 1.2 Layered Architecture **Standard:** Organize code into distinct layers (presentation, application, domain, infrastructure). * **Do This:** Separate concerns by grouping related functionality into loosely coupled layers. * **Don't Do This:** Mix presentation logic with business rules or database access code. **Why:** Layered architecture improves testability, maintainability, and reusability by isolating dependencies and responsibilities. **Code Example:** Layered approach to data handling with repositories """typescript // domain/user.ts export interface User { id: string; name: string; email: string; } // infrastructure/user-repository.ts import { User } from '../domain/user'; export interface UserRepository { getUserById(id: string): Promise<User | null>; saveUser(user: User): Promise<void>; } export class InMemoryUserRepository implements UserRepository { private users: { [id: string]: User } = {}; async getUserById(id: string): Promise<User | null> { return this.users[id] || null; } async saveUser(user: User): Promise<void> { this.users[user.id] = user; } } // application/user-service.ts import { UserRepository, InMemoryUserRepository } from '../infrastructure/user-repository'; import { User } from '../domain/user'; export class UserService { private userRepository: UserRepository; constructor(userRepository: UserRepository) { this.userRepository = userRepository; } async getUser(id: string): Promise<User | null> { return this.userRepository.getUserById(id); } async createUser(name: string, email: string): Promise<User> { const id = Math.random().toString(36).substring(2, 15); // generate a more robust ID for real applications const user: User = { id, name, email }; await this.userRepository.saveUser(user); return user; } } // presentation/user-controller.ts import { UserService } from '../application/user-service'; import { InMemoryUserRepository } from '../infrastructure/user-repository'; const userRepository = new InMemoryUserRepository(); const userService = new UserService(userRepository); async function main() { const newUser = await userService.createUser("John Doe", "john.doe@example.com"); const retrievedUser = await userService.getUser(newUser.id); console.log(retrievedUser); } main(); """ ### 1.3 Hexagonal Architecture (Ports and Adapters) **Standard:** Decouple the core business logic from external dependencies using ports and adapters. * **Do This:** Define interfaces (ports) for interacting with external systems (databases, APIs, UI). Implement adapters that translate between these interfaces and the specific technologies. * **Don't Do This:** Directly embed database queries or API calls within the core domain logic. **Why:** Hexagonal architecture makes it easier to switch technologies, test the core logic in isolation, and adapt to changing requirements. **Code Example:** Hexagonal Architecture using TypeScript interfaces & implementations """typescript // Interface -> Port interface PaymentGateway { processPayment(amount: number, creditCard: string): Promise<boolean>; } // Implementation -> Adapter for Stripe class StripePaymentGateway implements PaymentGateway { async processPayment(amount: number, creditCard: string): Promise<boolean> { // integrate with Stripe API here. For example: console.log("Processing $${amount} via Stripe with credit card ${creditCard}"); return true; // Simulated result } } // Implementation -> Adapter for PayPal class PayPalPaymentGateway implements PaymentGateway { async processPayment(amount: number, creditCard: string): Promise<boolean> { // integrate with PayPal API here console.log("Processing $${amount} via PayPal with credit card ${creditCard}"); return true; // Simulated result } } // Core Application Logic class PaymentService { private paymentGateway: PaymentGateway; constructor(paymentGateway: PaymentGateway) { this.paymentGateway = paymentGateway; } async charge(amount: number, creditCard: string): Promise<boolean> { return await this.paymentGateway.processPayment(amount, creditCard); } } // Usage: async function main() { const stripeGateway = new StripePaymentGateway(); const payPalGateway = new PayPalPaymentGateway(); const paymentServiceStripe = new PaymentService(stripeGateway); // Inject Stripe const paymentServicePayPal = new PaymentService(payPalGateway); // Inject PayPal const stripeResult = await paymentServiceStripe.charge(100, "1234-5678-9012-3456"); const paypalResult = await paymentServicePayPal.charge(50, "9876-5432-1098-7654"); console.log("Stripe Payment Result: ${stripeResult}"); console.log("PayPal Payment Result: ${paypalResult}"); } main(); """ ### 1.4 CQRS (Command Query Responsibility Segregation) **Standard:** Separate read operations (queries) from write operations (commands). * **Do This:** Define dedicated models and data access patterns for reading and writing data. * **Don't Do This:** Use the same data model and database queries for both reads and writes, potentially leading to performance issues and complex code. **Why:** CQRS allows optimizing read and write operations independently. This is particularly useful in scenarios with high read/write ratios. **Code Example:** Implementing CQRS pattern in TypeScript """typescript // Command: CreateUserCommand interface CreateUserCommand { type: 'CreateUser'; name: string; email: string; } // Query: GetUserQuery interface GetUserQuery { type: 'GetUser'; id: string; } // Command Handler class UserCommandHandler { async handle(command: CreateUserCommand): Promise<string> { if (command.type === 'CreateUser') { // Create user logic (e.g., save to database) const userId = Math.random().toString(36).substring(2, 15); console.log("Creating user with name ${command.name} and email ${command.email} (ID: ${userId})"); return userId // Return the new User ID } throw new Error('Invalid command'); } } // Query Handler class UserQueryHandler { async handle(query: GetUserQuery): Promise<{ id: string; name: string; email: string } | null > { if (query.type === 'GetUser') { // Retrieve user logic (e.g., fetch from database) console.log("Retrieving user with ID ${query.id}"); return { id: query.id, name: 'Example User', email: 'user@example.com' }; } throw new Error('Invalid query'); } } // Usage example: async function main() { const commandHandler = new UserCommandHandler(); const queryHandler = new UserQueryHandler(); const createUserCommand: CreateUserCommand = { type: 'CreateUser', name: 'John Doe', email: 'john.doe@example.com' }; const userId = await commandHandler.handle(createUserCommand); // creates a theoretical user console.log("User created with ID: ${userId}"); const getUserQuery: GetUserQuery = { type: 'GetUser', id: userId // Using the generated user ID }; const user = await queryHandler.handle(getUserQuery); if(user){ console.log("Retrieved user: ${JSON.stringify(user)}"); } else { console.log("User with ID ${getUserQuery.id} not found."); } } main(); """ ## 2. Project Structure and Organization ### 2.1 Modular File Structure **Standard:** Organize code into modules based on functionality, following a logical directory structure. * **Do This:** Group related files (components, services, interfaces) within dedicated directories. Use meaningful names for files and directories. Consider feature-based or layer-based structuring. * **Don't Do This:** Place all files in a single directory or create a folder structure that mirrors implementation details rather than business logic. **Why:** A modular structure improves code discoverability, maintainability, and collaboration among developers. """ src/ ├── components/ # Reusable UI components │ ├── button/ # Specific Button component │ │ ├── button.tsx # JSX code │ │ ├── button.module.css # Styles using CSS modules │ │ └── index.ts # Exports │ ├── input/ # Specific Input component │ │ ├── input.tsx # JSX code │ │ ├── input.module.css # Styles using CSS modules │ │ └── index.ts # Exports │ └── index.ts # Exports all reusable components ├── services/ # Business logic and API interactions │ ├── auth/ # Authentication-specific logic │ │ ├── auth-service.ts # Authentication service │ │ └── index.ts # Exports │ ├── api/ # API client │ │ ├── api-client.ts # API client │ │ └── index.ts # Exports │ └── index.ts # Exports all services ├── models/ # Data models / Types │ ├── user.ts # User model │ └── product.ts # Product model ├── utils/ # Utility functions │ ├── helper-functions.ts # Various utility functions │ └── index.ts # Exports ├── app.tsx # Main application component ├── styles/ # Global styles │ └── global.css ├── index.tsx # Entry point └── tsconfig.json # TypeScript configuration """ ### 2.2 Explicit Dependencies **Standard:** Declare all dependencies explicitly using "import" statements. * **Do This:** Import only the necessary modules or functions. Use named imports where possible. * **Don't Do This:** Rely on implicit global variables or wildcard imports ("import * as ..."). **Why:** Explicit dependencies allow for better code analysis, refactoring, and dependency management. **Code Example:** Named vs. wildcard imports """typescript // Good: Named imports import { useState, useEffect } from 'react'; import { calculateTotal } from './utils'; // Bad: Wildcard imports (less explicit) import * as React from 'react'; // Avoid unless necessary import * as Utils from './utils';// Avoid unless necessary """ ### 2.3 Single Responsibility Principle (SRP) **Standard:** Each module (class, function, component) should have one, and only one, reason to change. * **Do This:** Decompose complex functionalities into smaller, focused modules. * **Don't Do This:** Create "god classes" or functions that handle multiple unrelated tasks. **Why:** SRP simplifies code, improves testability, and reduces the risk of introducing unintended side effects when modifying existing code. **Code Example:** Adhering to Single Responsibility Principle """typescript // Good: Separate classes for user authentication and profile management // Class to Handle Authentication class AuthenticationService { async login(username: string, password: string): Promise<boolean> { // Authentication logic here console.log("Authenticating user ${username}"); return true; // Simulate successful authentication } async logout(): Promise<void> { // Logout logic here console.log('Logging out user'); } } // Class to Handle User Profiles class UserProfileService { async getUserProfile(userId: string): Promise<{ id: string; username: string; }> { // Logic to retrieve user profile console.log("Fetching profile for user ID ${userId}"); return { id: userId, username: 'exampleUser' }; // Simulate profile data } async updateUserProfile(userId: string, newData: any): Promise<void> { // Logic to update user profile console.log("Updating profile for user ID ${userId} with data:", newData); } } const authService = new AuthenticationService(); const profileService = new UserProfileService(); async function main() { const isLoggedIn = await authService.login('user123', 'password'); if (isLoggedIn) { const userProfile = await profileService.getUserProfile('user123'); console.log('User Profile:', userProfile); } } main(); """ ### 2.4 Separation of Concerns (SoC) **Standard:** Divide the application into distinct sections, each addressing a separate concern. * **Do This:** Isolate UI rendering from data fetching, business logic from infrastructure code, etc. * **Don't Do This:** Mix different concerns within the same module or component, leading to tightly coupled and difficult-to-maintain code. **Why:** SoC promotes modularity, testability, and reusability. **Code Example:** Separating data fetching concerns from UI """typescript // Bad: Mixing data fetching with UI rendering within the same component // (Leads to tight coupling and makes it difficult to test and reuse) // Consider isolating data fetching logic using custom hooks or services. import React, { useState, useEffect } from 'react'; interface User { id: number; name: string; email: string; } function UserProfileComponent({ userId }: { userId: string }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchUser() { try { const response = await fetch("https://jsonplaceholder.typicode.com/users/${userId}"); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data: User = await response.json(); setUser(data); } catch (error) { console.error('Error fetching user:', error); } finally { setLoading(false); } } fetchUser(); }, [userId]); if (loading) { return <div>Loading user profile...</div>; } if (!user) { return <div>Failed to load user profile.</div>; } // Rendering the user profile return ( <div> <h2>User Profile</h2> <p>Name: {user.name}</p> <p>Email: {user.email}</p> </div> ); } export default UserProfileComponent; // More robust example using custom hook import React from 'react'; import { useUser } from './useUser'; // Custom hook interface User { id: number; name: string; email: string; } function UserProfileComponentHooked({ userId }: { userId: string }) { const { user, loading, error } = useUser(userId); // Use the custom hook if (loading) { return <div>Loading user profile...</div>; } if (error) { return <div>Error: {error.message}</div>; } if (!user) { return <div>User not found.</div>; } return ( <div> <h2>User Profile</h2> <p>Name: {user.name}</p> <p>Email: {user.email}</p> </div> ); } export default UserProfileComponentHooked; // Custom hook import { useState, useEffect } from 'react'; interface User { id: number; name: string; email: string; } interface UseUserResult { user: User | null; loading: boolean; error: Error | null; } export function useUser(userId: string): UseUserResult { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { async function fetchUser() { setLoading(true); setError(null); try { const response = await fetch("https://jsonplaceholder.typicode.com/users/${userId}"); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data: User = await response.json(); setUser(data); } catch (e:any) { setError(e); } finally { setLoading(false); } } fetchUser(); }, [userId]); return { user, loading, error }; } """ ## 3. Design Patterns ### 3.1 Factory Pattern **Standard:** Use factory functions or classes to create objects, abstracting away the concrete implementation details. * **Do This:** Define a factory interface or abstract class and create concrete factories that implement the interface. * **Don't Do This:** Directly instantiate concrete classes throughout the codebase, tightly coupling code to specific implementations. **Why:** The Factory Pattern promotes loose coupling and allows for easy substitution of object implementations. **Code Example:** Factory pattern in typescript """typescript // Interface for different payment methods interface PaymentMethod { processPayment(amount: number): void; } // Concrete implementation for Credit Card payment class CreditCardPayment implements PaymentMethod { processPayment(amount: number): void { console.log("Processing credit card payment of $${amount}"); } } // Concrete implementation for PayPal payment class PayPalPayment implements PaymentMethod { processPayment(amount: number): void { console.log("Processing PayPal payment of $${amount}"); } } // Factory interface interface PaymentMethodFactory { createPaymentMethod(): PaymentMethod; } // Concrete factory for creating Credit Card payments class CreditCardPaymentFactory implements PaymentMethodFactory { createPaymentMethod(): PaymentMethod { return new CreditCardPayment(); } } // Concrete factory for creating PayPal payments class PayPalPaymentFactory implements PaymentMethodFactory { createPaymentMethod(): PaymentMethod { return new PayPalPayment(); } } // The client code that uses the factory to create payment methods class PaymentProcessor { private factory: PaymentMethodFactory; constructor(factory: PaymentMethodFactory) { this.factory = factory; } processOrder(amount: number): void { const paymentMethod = this.factory.createPaymentMethod(); paymentMethod.processPayment(amount); } } // Usage async function main() { const creditCardFactory = new CreditCardPaymentFactory(); const payPalFactory = new PayPalPaymentFactory(); const paymentProcessor1 = new PaymentProcessor(creditCardFactory); paymentProcessor1.processOrder(100); // Output: Processing credit card payment of $100 const paymentProcessor2 = new PaymentProcessor(payPalFactory); paymentProcessor2.processOrder(50); // Output: Processing PayPal payment of $50 } main(); """ ### 3.2 Observer Pattern **Standard:** Define a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically. * **Do This:** Create a "Subject" interface that provides methods for attaching and detaching observers * **Don't Do This:** Have tight coupling between objects by directly calling methods on other objects. **Why:** The Observer Pattern decouples the subject from its observers, making it easier to add or remove observers without modifying the subject. **Code Example:** Implementation """typescript // Observer Interface interface Observer { update(message: string): void; } // Subject Interface interface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(message: string): void; } // Concrete Observer class ConcreteObserver implements Observer { private id: number; constructor(id: number) { this.id = id; } update(message: string): void { console.log("Observer ${this.id}: Received message - ${message}"); } } // Concrete Subject class ConcreteSubject implements Subject { private observers: Observer[] = []; attach(observer: Observer): void { this.observers.push(observer); } detach(observer: Observer): void { this.observers = this.observers.filter(obs => obs !== observer); } notify(message: string): void { this.observers.forEach(observer => observer.update(message)); } // Method to update state and notify observers someBusinessLogic(): void { console.log('Subject: Doing something important.'); this.notify('Important message from Subject!'); } } // Usage async function main() { const subject = new ConcreteSubject(); const observer1 = new ConcreteObserver(1); const observer2 = new ConcreteObserver(2); const observer3 = new ConcreteObserver(3); subject.attach(observer1); subject.attach(observer2); subject.someBusinessLogic(); subject.detach(observer2); // Detach observer2 subject.attach(observer3); subject.someBusinessLogic(); } main(); """ ### 3.3 Strategy Pattern **Standard:** Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. * **Do This:** Create a strategy interface that defines a method the strategies must implement, then each concrete strategy implements this interface. * **Don't Do This:** Hardcode the execution path. **Why:** To encapsulate variation. **Code Example:** Implementation """typescript // Strategy Interface interface SortStrategy { sort(data: number[]): number[]; } // Concrete Strategy 1: Bubble Sort class BubbleSortStrategy implements SortStrategy { sort(data: number[]): number[] { console.log('Sorting using bubble sort'); // Bubble Sort implementation const n = data.length; for (let i = 0; i < n - 1; i++) { for (let j = 0; j < n - i - 1; j++) { if (data[j] > data[j + 1]) { // Swap data[j] and data[j+1] const temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } } return data; } } // Concrete Strategy 2: Quick Sort class QuickSortStrategy implements SortStrategy { sort(data: number[]): number[] { console.log('Sorting using quick sort'); // Quick Sort implementation if (data.length <= 1) { return data; } const pivot = data[0]; const left = []; const right = []; for (let i = 1; i < data.length; i++) { if (data[i] < pivot) { left.push(data[i]); } else { right.push(data[i]); } } return this.sort(left).concat(pivot, this.sort(right)); } } // Context class Sorter { private strategy: SortStrategy; constructor(strategy: SortStrategy) { this.strategy = strategy; } setStrategy(strategy: SortStrategy): void { this.strategy = strategy; } sort(data: number[]): number[] { return this.strategy.sort(data); } } // Usage async function main() { const data = [5, 2, 8, 1, 9, 4]; const bubbleSort = new BubbleSortStrategy(); const quickSort = new QuickSortStrategy(); const sorter = new Sorter(bubbleSort); // Initial strategy is Bubble Sort console.log('Sorted array using Bubble Sort:', sorter.sort([...data])); sorter.setStrategy(quickSort); // Change the strategy to Quick Sort console.log('Sorted array using Quick Sort:', sorter.sort([...data])); } main(); """ ## 4. TypeScript Specific Considerations ### 4.1 Strict Mode **Standard:** Enable TypeScript's strict mode ("strict: true" in "tsconfig.json"). * **Do This:** Embrace strict null checks, no implicit "any", and other strictness flags. Fix related typing issues. * **Don't Do This:** Disable strict mode to avoid compiler errors, as this can hide potential runtime bugs. **Why:** Strict mode enforces stricter type checking, leading to more robust and maintainable code. ### 4.2 Utility Types **Standard:** Leverage TypeScript's utility types (e.g., "Partial", "Readonly", "Pick", "Omit") to manipulate types and improve code clarity. """typescript interface User { id: string; name: string; email: string; createdAt: Date; } // Make all properties optional type PartialUser = Partial<User>; // Make all properties readonly type ReadonlyUser = Readonly<User>; // Pick only id and name properties type UserName = Pick<User, 'id' | 'name'>; // Omit the createdAt property type UserWithoutCreatedAt = Omit<User, 'createdAt'>; """ ### 4.3 Type Inference **Standard:** Take advantage of TypeScript's type inference capabilities to reduce boilerplate and improve code readability. **Code Example:** """typescript // TypeScript infers the type of 'message' to be string const message = "Hello, TypeScript!"; // TypeScript infers the return type of the function function add(a: number, b: number) { return a + b; } """ ### 4.4 Declaration Files **Standard:** Provide declaration files (".d.ts") for libraries or modules to enable type checking and code completion for consumers. * **Do This:** Use automatic declaration generation ("declaration: true" in "tsconfig.json") or manually create declaration files when necessary. * **Don't Do This:** Ship JavaScript code without corresponding declaration files, limiting the usability of the code in TypeScript projects. ### 4.5 Advanced Types **Standard:** Use advanced TypeScript features like discriminated unions, conditional types, and mapped types where appropriate to model complex data structures and relationships. """typescript // Discriminated Union interface Circle { kind: 'circle'; radius: number; } interface Square { kind: 'square'; sideLength: number; } type Shape = Circle | Square; function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; case 'square': return shape.sideLength ** 2; } } """ ### 4.6 Decorators **Standard:** Use decorators to add metadata or modify the behavior of classes, methods, or properties, but adhere to a consistent style, avoid complex implementations, and be aware of potential performance implications. """typescript function LogClass(constructor: Function) { console.log("Class ${constructor.name} is being decorated."); } @LogClass class MyClass { constructor() { console.log('MyClass constructor called.'); } } function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log("Method ${propertyKey} is being called with arguments:", args); const result = originalMethod.apply(this, args); console.log("Method ${propertyKey} returned:", result); return result; }; return descriptor; } class Calculator { @LogMethod add(a: number, b: number): number { return a + b; } } """ ### 4.7 Use "unknown" type where appropriate **Standard:** Use "unknown" instead of "any" when you need to represent a value of any type but want to ensure type safety. "unknown" forces you to perform type narrowing before using the value, providing better type safety than casually using "any", while not necessarily knowing the implementation types at compile time. """typescript function processData(data: unknown): void { if (typeof data === 'string') { console.log(data.toUpperCase()); // OK, data is string here } else if (typeof data === 'number') { console.log(data * 2); // OK, data is number here } else { console.log('Data is of unknown type'); } } """ These core architecture standards provide a comprehensive foundation for building robust, scalable, and maintainable TypeScript applications. By adhering to these guidelines, development teams can improve code quality, reduce technical debt, and accelerate development.