style: format all files with prettier

This commit is contained in:
Seth Hobson
2026-01-19 17:07:03 -05:00
parent 8d37048deb
commit 56848874a2
355 changed files with 15215 additions and 10241 deletions

View File

@@ -7,6 +7,7 @@ model: opus
You are a TypeScript expert specializing in advanced typing and enterprise-grade development.
## Focus Areas
- Advanced type systems (generics, conditional types, mapped types)
- Strict TypeScript configuration and compiler options
- Type inference optimization and utility types
@@ -15,6 +16,7 @@ You are a TypeScript expert specializing in advanced typing and enterprise-grade
- Integration with modern frameworks (React, Node.js, Express)
## Approach
1. Leverage strict type checking with appropriate compiler flags
2. Use generics and utility types for maximum type safety
3. Prefer type inference over explicit annotations when clear
@@ -23,6 +25,7 @@ You are a TypeScript expert specializing in advanced typing and enterprise-grade
6. Optimize build times with incremental compilation
## Output
- Strongly-typed TypeScript with comprehensive interfaces
- Generic functions and classes with proper constraints
- Custom utility types and advanced type manipulations

View File

@@ -15,6 +15,7 @@ $ARGUMENTS
### 1. Analyze Project Type
Determine the project type from user requirements:
- **Next.js**: Full-stack React applications, SSR/SSG, API routes
- **React + Vite**: SPA applications, component libraries
- **Node.js API**: Express/Fastify backends, microservices
@@ -82,6 +83,7 @@ nextjs-project/
```
**package.json**:
```json
{
"name": "nextjs-project",
@@ -112,6 +114,7 @@ nextjs-project/
```
**tsconfig.json**:
```json
{
"compilerOptions": {
@@ -131,7 +134,7 @@ nextjs-project/
"paths": {
"@/*": ["./src/*"]
},
"plugins": [{"name": "next"}]
"plugins": [{ "name": "next" }]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
@@ -146,16 +149,17 @@ pnpm create vite . --template react-ts
```
**vite.config.ts**:
```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
"@": path.resolve(__dirname, "./src"),
},
},
server: {
@@ -163,10 +167,10 @@ export default defineConfig({
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setup.ts',
environment: "jsdom",
setupFiles: "./tests/setup.ts",
},
})
});
```
### 5. Generate Node.js API Project Structure
@@ -202,6 +206,7 @@ nodejs-api/
```
**package.json for Node.js API**:
```json
{
"name": "nodejs-api",
@@ -233,21 +238,22 @@ nodejs-api/
```
**src/app.ts**:
```typescript
import express, { Express } from 'express'
import { healthRouter } from './routes/health.js'
import { userRouter } from './routes/users.js'
import { errorHandler } from './middleware/errorHandler.js'
import express, { Express } from "express";
import { healthRouter } from "./routes/health.js";
import { userRouter } from "./routes/users.js";
import { errorHandler } from "./middleware/errorHandler.js";
export function createApp(): Express {
const app = express()
const app = express();
app.use(express.json())
app.use('/health', healthRouter)
app.use('/api/users', userRouter)
app.use(errorHandler)
app.use(express.json());
app.use("/health", healthRouter);
app.use("/api/users", userRouter);
app.use(errorHandler);
return app
return app;
}
```
@@ -267,6 +273,7 @@ library-name/
```
**package.json for Library**:
```json
{
"name": "@scope/library-name",
@@ -296,6 +303,7 @@ library-name/
### 7. Configure Development Tools
**.env.example**:
```env
NODE_ENV=development
PORT=3000
@@ -304,29 +312,28 @@ JWT_SECRET=your-secret-key
```
**vitest.config.ts**:
```typescript
import { defineConfig } from 'vitest/config'
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: 'node',
environment: "node",
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
provider: "v8",
reporter: ["text", "json", "html"],
},
},
})
});
```
**.eslintrc.json**:
```json
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "error"

View File

@@ -23,19 +23,20 @@ Comprehensive guide for implementing robust testing strategies in JavaScript/Typ
### Jest - Full-Featured Testing Framework
**Setup:**
```typescript
// jest.config.ts
import type { Config } from 'jest';
import type { Config } from "jest";
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.interface.ts',
"src/**/*.ts",
"!src/**/*.d.ts",
"!src/**/*.interface.ts",
],
coverageThreshold: {
global: {
@@ -45,7 +46,7 @@ const config: Config = {
statements: 80,
},
},
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
setupFilesAfterEnv: ["<rootDir>/src/test/setup.ts"],
};
export default config;
@@ -54,20 +55,21 @@ export default config;
### Vitest - Fast, Vite-Native Testing
**Setup:**
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: 'node',
environment: "node",
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.d.ts', '**/*.config.ts', '**/dist/**'],
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["**/*.d.ts", "**/*.config.ts", "**/dist/**"],
},
setupFiles: ['./src/test/setup.ts'],
setupFiles: ["./src/test/setup.ts"],
},
});
```
@@ -84,42 +86,42 @@ export function add(a: number, b: number): number {
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero');
throw new Error("Division by zero");
}
return a / b;
}
// utils/calculator.test.ts
import { describe, it, expect } from 'vitest';
import { add, divide } from './calculator';
import { describe, it, expect } from "vitest";
import { add, divide } from "./calculator";
describe('Calculator', () => {
describe('add', () => {
it('should add two positive numbers', () => {
describe("Calculator", () => {
describe("add", () => {
it("should add two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it('should add negative numbers', () => {
it("should add negative numbers", () => {
expect(add(-2, -3)).toBe(-5);
});
it('should handle zero', () => {
it("should handle zero", () => {
expect(add(0, 5)).toBe(5);
expect(add(5, 0)).toBe(5);
});
});
describe('divide', () => {
it('should divide two numbers', () => {
describe("divide", () => {
it("should divide two numbers", () => {
expect(divide(10, 2)).toBe(5);
});
it('should handle decimal results', () => {
it("should handle decimal results", () => {
expect(divide(5, 2)).toBe(2.5);
});
it('should throw error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
it("should throw error when dividing by zero", () => {
expect(() => divide(10, 0)).toThrow("Division by zero");
});
});
});
@@ -134,7 +136,7 @@ export class UserService {
create(user: User): User {
if (this.users.has(user.id)) {
throw new Error('User already exists');
throw new Error("User already exists");
}
this.users.set(user.id, user);
return user;
@@ -147,7 +149,7 @@ export class UserService {
update(id: string, updates: Partial<User>): User {
const user = this.users.get(id);
if (!user) {
throw new Error('User not found');
throw new Error("User not found");
}
const updated = { ...user, ...updates };
this.users.set(id, updated);
@@ -160,47 +162,48 @@ export class UserService {
}
// services/user.service.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { UserService } from './user.service';
import { describe, it, expect, beforeEach } from "vitest";
import { UserService } from "./user.service";
describe('UserService', () => {
describe("UserService", () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
describe('create', () => {
it('should create a new user', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
describe("create", () => {
it("should create a new user", () => {
const user = { id: "1", name: "John", email: "john@example.com" };
const created = service.create(user);
expect(created).toEqual(user);
expect(service.findById('1')).toEqual(user);
expect(service.findById("1")).toEqual(user);
});
it('should throw error if user already exists', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
it("should throw error if user already exists", () => {
const user = { id: "1", name: "John", email: "john@example.com" };
service.create(user);
expect(() => service.create(user)).toThrow('User already exists');
expect(() => service.create(user)).toThrow("User already exists");
});
});
describe('update', () => {
it('should update existing user', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
describe("update", () => {
it("should update existing user", () => {
const user = { id: "1", name: "John", email: "john@example.com" };
service.create(user);
const updated = service.update('1', { name: 'Jane' });
const updated = service.update("1", { name: "Jane" });
expect(updated.name).toBe('Jane');
expect(updated.email).toBe('john@example.com');
expect(updated.name).toBe("Jane");
expect(updated.email).toBe("john@example.com");
});
it('should throw error if user not found', () => {
expect(() => service.update('999', { name: 'Jane' }))
.toThrow('User not found');
it("should throw error if user not found", () => {
expect(() => service.update("999", { name: "Jane" })).toThrow(
"User not found",
);
});
});
});
@@ -214,15 +217,15 @@ export class ApiService {
async fetchUser(id: string): Promise<User> {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
throw new Error("User not found");
}
return response.json();
}
async createUser(user: CreateUserDTO): Promise<User> {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user),
});
return response.json();
@@ -230,13 +233,13 @@ export class ApiService {
}
// services/api.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ApiService } from './api.service';
import { describe, it, expect, vi, beforeEach } from "vitest";
import { ApiService } from "./api.service";
// Mock fetch globally
global.fetch = vi.fn();
describe('ApiService', () => {
describe("ApiService", () => {
let service: ApiService;
beforeEach(() => {
@@ -244,34 +247,34 @@ describe('ApiService', () => {
vi.clearAllMocks();
});
describe('fetchUser', () => {
it('should fetch user successfully', async () => {
const mockUser = { id: '1', name: 'John', email: 'john@example.com' };
describe("fetchUser", () => {
it("should fetch user successfully", async () => {
const mockUser = { id: "1", name: "John", email: "john@example.com" };
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => mockUser,
});
const user = await service.fetchUser('1');
const user = await service.fetchUser("1");
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
expect(fetch).toHaveBeenCalledWith("https://api.example.com/users/1");
});
it('should throw error if user not found', async () => {
it("should throw error if user not found", async () => {
(fetch as any).mockResolvedValueOnce({
ok: false,
});
await expect(service.fetchUser('999')).rejects.toThrow('User not found');
await expect(service.fetchUser("999")).rejects.toThrow("User not found");
});
});
describe('createUser', () => {
it('should create user successfully', async () => {
const newUser = { name: 'John', email: 'john@example.com' };
const createdUser = { id: '1', ...newUser };
describe("createUser", () => {
it("should create user successfully", async () => {
const newUser = { name: "John", email: "john@example.com" };
const createdUser = { id: "1", ...newUser };
(fetch as any).mockResolvedValueOnce({
ok: true,
@@ -282,11 +285,11 @@ describe('ApiService', () => {
expect(user).toEqual(createdUser);
expect(fetch).toHaveBeenCalledWith(
'https://api.example.com/users',
"https://api.example.com/users",
expect.objectContaining({
method: 'POST',
method: "POST",
body: JSON.stringify(newUser),
})
}),
);
});
});
@@ -299,7 +302,7 @@ describe('ApiService', () => {
```typescript
// services/email.service.ts
import nodemailer from 'nodemailer';
import nodemailer from "nodemailer";
export class EmailService {
private transporter = nodemailer.createTransport({
@@ -322,36 +325,36 @@ export class EmailService {
}
// services/email.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { EmailService } from './email.service';
import { describe, it, expect, vi, beforeEach } from "vitest";
import { EmailService } from "./email.service";
vi.mock('nodemailer', () => ({
vi.mock("nodemailer", () => ({
default: {
createTransport: vi.fn(() => ({
sendMail: vi.fn().mockResolvedValue({ messageId: '123' }),
sendMail: vi.fn().mockResolvedValue({ messageId: "123" }),
})),
},
}));
describe('EmailService', () => {
describe("EmailService", () => {
let service: EmailService;
beforeEach(() => {
service = new EmailService();
});
it('should send email successfully', async () => {
it("should send email successfully", async () => {
await service.sendEmail(
'test@example.com',
'Test Subject',
'<p>Test Body</p>'
"test@example.com",
"Test Subject",
"<p>Test Body</p>",
);
expect(service['transporter'].sendMail).toHaveBeenCalledWith(
expect(service["transporter"].sendMail).toHaveBeenCalledWith(
expect.objectContaining({
to: 'test@example.com',
subject: 'Test Subject',
})
to: "test@example.com",
subject: "Test Subject",
}),
);
});
});
@@ -372,7 +375,7 @@ export class UserService {
async getUser(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
throw new Error("User not found");
}
return user;
}
@@ -385,10 +388,10 @@ export class UserService {
}
// services/user.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserService, IUserRepository } from './user.service';
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserService, IUserRepository } from "./user.service";
describe('UserService', () => {
describe("UserService", () => {
let service: UserService;
let mockRepository: IUserRepository;
@@ -400,28 +403,28 @@ describe('UserService', () => {
service = new UserService(mockRepository);
});
describe('getUser', () => {
it('should return user if found', async () => {
const mockUser = { id: '1', name: 'John', email: 'john@example.com' };
describe("getUser", () => {
it("should return user if found", async () => {
const mockUser = { id: "1", name: "John", email: "john@example.com" };
vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);
const user = await service.getUser('1');
const user = await service.getUser("1");
expect(user).toEqual(mockUser);
expect(mockRepository.findById).toHaveBeenCalledWith('1');
expect(mockRepository.findById).toHaveBeenCalledWith("1");
});
it('should throw error if user not found', async () => {
it("should throw error if user not found", async () => {
vi.mocked(mockRepository.findById).mockResolvedValue(null);
await expect(service.getUser('999')).rejects.toThrow('User not found');
await expect(service.getUser("999")).rejects.toThrow("User not found");
});
});
describe('createUser', () => {
it('should create user successfully', async () => {
const userData = { name: 'John', email: 'john@example.com' };
const createdUser = { id: '1', ...userData };
describe("createUser", () => {
it("should create user successfully", async () => {
const userData = { name: "John", email: "john@example.com" };
const createdUser = { id: "1", ...userData };
vi.mocked(mockRepository.create).mockResolvedValue(createdUser);
@@ -444,7 +447,7 @@ export const logger = {
};
// services/order.service.ts
import { logger } from '../utils/logger';
import { logger } from "../utils/logger";
export class OrderService {
async processOrder(orderId: string): Promise<void> {
@@ -455,28 +458,28 @@ export class OrderService {
}
// services/order.service.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { OrderService } from './order.service';
import { logger } from '../utils/logger';
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { OrderService } from "./order.service";
import { logger } from "../utils/logger";
describe('OrderService', () => {
describe("OrderService", () => {
let service: OrderService;
let loggerSpy: any;
beforeEach(() => {
service = new OrderService();
loggerSpy = vi.spyOn(logger, 'info');
loggerSpy = vi.spyOn(logger, "info");
});
afterEach(() => {
loggerSpy.mockRestore();
});
it('should log order processing', async () => {
await service.processOrder('123');
it("should log order processing", async () => {
await service.processOrder("123");
expect(loggerSpy).toHaveBeenCalledWith('Processing order 123');
expect(loggerSpy).toHaveBeenCalledWith('Order 123 processed successfully');
expect(loggerSpy).toHaveBeenCalledWith("Processing order 123");
expect(loggerSpy).toHaveBeenCalledWith("Order 123 processed successfully");
expect(loggerSpy).toHaveBeenCalledTimes(2);
});
});
@@ -488,37 +491,37 @@ describe('OrderService', () => {
```typescript
// tests/integration/user.api.test.ts
import request from 'supertest';
import { app } from '../../src/app';
import { pool } from '../../src/config/database';
import request from "supertest";
import { app } from "../../src/app";
import { pool } from "../../src/config/database";
describe('User API Integration Tests', () => {
describe("User API Integration Tests", () => {
beforeAll(async () => {
// Setup test database
await pool.query('CREATE TABLE IF NOT EXISTS users (...)');
await pool.query("CREATE TABLE IF NOT EXISTS users (...)");
});
afterAll(async () => {
// Cleanup
await pool.query('DROP TABLE IF EXISTS users');
await pool.query("DROP TABLE IF EXISTS users");
await pool.end();
});
beforeEach(async () => {
// Clear data before each test
await pool.query('TRUNCATE TABLE users CASCADE');
await pool.query("TRUNCATE TABLE users CASCADE");
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
describe("POST /api/users", () => {
it("should create a new user", async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
name: "John Doe",
email: "john@example.com",
password: "password123",
};
const response = await request(app)
.post('/api/users')
.post("/api/users")
.send(userData)
.expect(201);
@@ -526,52 +529,50 @@ describe('User API Integration Tests', () => {
name: userData.name,
email: userData.email,
});
expect(response.body).toHaveProperty('id');
expect(response.body).not.toHaveProperty('password');
expect(response.body).toHaveProperty("id");
expect(response.body).not.toHaveProperty("password");
});
it('should return 400 if email is invalid', async () => {
it("should return 400 if email is invalid", async () => {
const userData = {
name: 'John Doe',
email: 'invalid-email',
password: 'password123',
name: "John Doe",
email: "invalid-email",
password: "password123",
};
const response = await request(app)
.post('/api/users')
.post("/api/users")
.send(userData)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body).toHaveProperty("error");
});
it('should return 409 if email already exists', async () => {
it("should return 409 if email already exists", async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
name: "John Doe",
email: "john@example.com",
password: "password123",
};
await request(app).post('/api/users').send(userData);
await request(app).post("/api/users").send(userData);
const response = await request(app)
.post('/api/users')
.post("/api/users")
.send(userData)
.expect(409);
expect(response.body.error).toContain('already exists');
expect(response.body.error).toContain("already exists");
});
});
describe('GET /api/users/:id', () => {
it('should get user by id', async () => {
const createResponse = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
});
describe("GET /api/users/:id", () => {
it("should get user by id", async () => {
const createResponse = await request(app).post("/api/users").send({
name: "John Doe",
email: "john@example.com",
password: "password123",
});
const userId = createResponse.body.id;
@@ -581,50 +582,42 @@ describe('User API Integration Tests', () => {
expect(response.body).toMatchObject({
id: userId,
name: 'John Doe',
email: 'john@example.com',
name: "John Doe",
email: "john@example.com",
});
});
it('should return 404 if user not found', async () => {
await request(app)
.get('/api/users/999')
.expect(404);
it("should return 404 if user not found", async () => {
await request(app).get("/api/users/999").expect(404);
});
});
describe('Authentication', () => {
it('should require authentication for protected routes', async () => {
await request(app)
.get('/api/users/me')
.expect(401);
describe("Authentication", () => {
it("should require authentication for protected routes", async () => {
await request(app).get("/api/users/me").expect(401);
});
it('should allow access with valid token', async () => {
it("should allow access with valid token", async () => {
// Create user and login
await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
});
await request(app).post("/api/users").send({
name: "John Doe",
email: "john@example.com",
password: "password123",
});
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'john@example.com',
password: 'password123',
});
const loginResponse = await request(app).post("/api/auth/login").send({
email: "john@example.com",
password: "password123",
});
const token = loginResponse.body.token;
const response = await request(app)
.get('/api/users/me')
.set('Authorization', `Bearer ${token}`)
.get("/api/users/me")
.set("Authorization", `Bearer ${token}`)
.expect(200);
expect(response.body.email).toBe('john@example.com');
expect(response.body.email).toBe("john@example.com");
});
});
});
@@ -634,21 +627,21 @@ describe('User API Integration Tests', () => {
```typescript
// tests/integration/user.repository.test.ts
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { Pool } from 'pg';
import { UserRepository } from '../../src/repositories/user.repository';
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
import { Pool } from "pg";
import { UserRepository } from "../../src/repositories/user.repository";
describe('UserRepository Integration Tests', () => {
describe("UserRepository Integration Tests", () => {
let pool: Pool;
let repository: UserRepository;
beforeAll(async () => {
pool = new Pool({
host: 'localhost',
host: "localhost",
port: 5432,
database: 'test_db',
user: 'test_user',
password: 'test_password',
database: "test_db",
user: "test_user",
password: "test_password",
});
repository = new UserRepository(pool);
@@ -666,41 +659,41 @@ describe('UserRepository Integration Tests', () => {
});
afterAll(async () => {
await pool.query('DROP TABLE IF EXISTS users');
await pool.query("DROP TABLE IF EXISTS users");
await pool.end();
});
beforeEach(async () => {
await pool.query('TRUNCATE TABLE users CASCADE');
await pool.query("TRUNCATE TABLE users CASCADE");
});
it('should create a user', async () => {
it("should create a user", async () => {
const user = await repository.create({
name: 'John Doe',
email: 'john@example.com',
password: 'hashed_password',
name: "John Doe",
email: "john@example.com",
password: "hashed_password",
});
expect(user).toHaveProperty('id');
expect(user.name).toBe('John Doe');
expect(user.email).toBe('john@example.com');
expect(user).toHaveProperty("id");
expect(user.name).toBe("John Doe");
expect(user.email).toBe("john@example.com");
});
it('should find user by email', async () => {
it("should find user by email", async () => {
await repository.create({
name: 'John Doe',
email: 'john@example.com',
password: 'hashed_password',
name: "John Doe",
email: "john@example.com",
password: "hashed_password",
});
const user = await repository.findByEmail('john@example.com');
const user = await repository.findByEmail("john@example.com");
expect(user).toBeTruthy();
expect(user?.name).toBe('John Doe');
expect(user?.name).toBe("John Doe");
});
it('should return null if user not found', async () => {
const user = await repository.findByEmail('nonexistent@example.com');
it("should return null if user not found", async () => {
const user = await repository.findByEmail("nonexistent@example.com");
expect(user).toBeNull();
});
});
@@ -799,7 +792,7 @@ describe('UserForm', () => {
```typescript
// hooks/useCounter.ts
import { useState, useCallback } from 'react';
import { useState, useCallback } from "react";
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
@@ -812,22 +805,22 @@ export function useCounter(initialValue = 0) {
}
// hooks/useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { useCounter } from './useCounter';
import { renderHook, act } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { useCounter } from "./useCounter";
describe('useCounter', () => {
it('should initialize with default value', () => {
describe("useCounter", () => {
it("should initialize with default value", () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('should initialize with custom value', () => {
it("should initialize with custom value", () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it('should increment count', () => {
it("should increment count", () => {
const { result } = renderHook(() => useCounter());
act(() => {
@@ -837,7 +830,7 @@ describe('useCounter', () => {
expect(result.current.count).toBe(1);
});
it('should decrement count', () => {
it("should decrement count", () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
@@ -847,7 +840,7 @@ describe('useCounter', () => {
expect(result.current.count).toBe(4);
});
it('should reset to initial value', () => {
it("should reset to initial value", () => {
const { result } = renderHook(() => useCounter(10));
act(() => {
@@ -870,7 +863,7 @@ describe('useCounter', () => {
```typescript
// tests/fixtures/user.fixture.ts
import { faker } from '@faker-js/faker';
import { faker } from "@faker-js/faker";
export function createUserFixture(overrides?: Partial<User>): User {
return {
@@ -887,15 +880,18 @@ export function createUsersFixture(count: number): User[] {
}
// Usage in tests
import { createUserFixture, createUsersFixture } from '../fixtures/user.fixture';
import {
createUserFixture,
createUsersFixture,
} from "../fixtures/user.fixture";
describe('UserService', () => {
it('should process user', () => {
const user = createUserFixture({ name: 'John Doe' });
describe("UserService", () => {
it("should process user", () => {
const user = createUserFixture({ name: "John Doe" });
// Use user in test
});
it('should handle multiple users', () => {
it("should handle multiple users", () => {
const users = createUsersFixture(10);
// Use users in test
});
@@ -967,16 +963,16 @@ describe('UserCard', () => {
### Test Organization
```typescript
describe('UserService', () => {
describe('createUser', () => {
it('should create user successfully', () => {});
it('should throw error if email exists', () => {});
it('should hash password', () => {});
describe("UserService", () => {
describe("createUser", () => {
it("should create user successfully", () => {});
it("should throw error if email exists", () => {});
it("should hash password", () => {});
});
describe('updateUser', () => {
it('should update user', () => {});
it('should throw error if not found', () => {});
describe("updateUser", () => {
it("should update user", () => {});
it("should throw error if not found", () => {});
});
});
```
@@ -985,23 +981,23 @@ describe('UserService', () => {
```typescript
// Using async/await
it('should fetch user', async () => {
const user = await service.fetchUser('1');
it("should fetch user", async () => {
const user = await service.fetchUser("1");
expect(user).toBeDefined();
});
// Testing rejections
it('should throw error', async () => {
await expect(service.fetchUser('invalid')).rejects.toThrow('Not found');
it("should throw error", async () => {
await expect(service.fetchUser("invalid")).rejects.toThrow("Not found");
});
```
### Testing Timers
```typescript
import { vi } from 'vitest';
import { vi } from "vitest";
it('should call function after delay', () => {
it("should call function after delay", () => {
vi.useFakeTimers();
const callback = vi.fn();

View File

@@ -23,6 +23,7 @@ Comprehensive guide for mastering modern JavaScript (ES6+) features, functional
### 1. Arrow Functions
**Syntax and Use Cases:**
```javascript
// Traditional function
function add(a, b) {
@@ -33,13 +34,13 @@ function add(a, b) {
const add = (a, b) => a + b;
// Single parameter (parentheses optional)
const double = x => x * 2;
const double = (x) => x * 2;
// No parameters
const getRandom = () => Math.random();
// Multiple statements (need curly braces)
const processUser = user => {
const processUser = (user) => {
const normalized = user.name.toLowerCase();
return { ...user, name: normalized };
};
@@ -49,6 +50,7 @@ const createUser = (name, age) => ({ name, age });
```
**Lexical 'this' Binding:**
```javascript
class Counter {
constructor() {
@@ -62,15 +64,15 @@ class Counter {
// Traditional function loses 'this' in callbacks
incrementTraditional() {
setTimeout(function() {
this.count++; // 'this' is undefined
setTimeout(function () {
this.count++; // 'this' is undefined
}, 1000);
}
// Arrow function maintains 'this'
incrementArrow() {
setTimeout(() => {
this.count++; // 'this' refers to Counter instance
this.count++; // 'this' refers to Counter instance
}, 1000);
}
}
@@ -79,15 +81,16 @@ class Counter {
### 2. Destructuring
**Object Destructuring:**
```javascript
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
name: "John Doe",
email: "john@example.com",
address: {
city: 'New York',
country: 'USA'
}
city: "New York",
country: "USA",
},
};
// Basic destructuring
@@ -100,7 +103,9 @@ const { name: userName, email: userEmail } = user;
const { age = 25 } = user;
// Nested destructuring
const { address: { city, country } } = user;
const {
address: { city, country },
} = user;
// Rest operator
const { id, ...userWithoutId } = user;
@@ -113,6 +118,7 @@ greet(user);
```
**Array Destructuring:**
```javascript
const numbers = [1, 2, 3, 4, 5];
@@ -126,7 +132,8 @@ const [, , third] = numbers;
const [head, ...tail] = numbers;
// Swapping variables
let a = 1, b = 2;
let a = 1,
b = 2;
[a, b] = [b, a];
// Function return values
@@ -142,6 +149,7 @@ const [one, two, three = 0] = [1, 2];
### 3. Spread and Rest Operators
**Spread Operator:**
```javascript
// Array spreading
const arr1 = [1, 2, 3];
@@ -149,8 +157,8 @@ const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
// Object spreading
const defaults = { theme: 'dark', lang: 'en' };
const userPrefs = { theme: 'light' };
const defaults = { theme: "dark", lang: "en" };
const userPrefs = { theme: "light" };
const settings = { ...defaults, ...userPrefs };
// Function arguments
@@ -167,6 +175,7 @@ const newObj = { ...user, age: 30 };
```
**Rest Parameters:**
```javascript
// Collect function arguments
function sum(...numbers) {
@@ -176,9 +185,9 @@ sum(1, 2, 3, 4, 5);
// With regular parameters
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}`;
return `${greeting} ${names.join(", ")}`;
}
greet('Hello', 'John', 'Jane', 'Bob');
greet("Hello", "John", "Jane", "Bob");
// Object rest
const { id, ...userData } = user;
@@ -191,7 +200,7 @@ const [first, ...rest] = [1, 2, 3, 4, 5];
```javascript
// Basic usage
const name = 'John';
const name = "John";
const greeting = `Hello, ${name}!`;
// Multi-line strings
@@ -209,12 +218,12 @@ const total = `Total: $${(price * 1.2).toFixed(2)}`;
// Tagged template literals
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
const value = values[i] || "";
return result + str + `<mark>${value}</mark>`;
}, '');
}, "");
}
const name = 'John';
const name = "John";
const age = 30;
const html = highlight`Name: ${name}, Age: ${age}`;
// Output: "Name: <mark>John</mark>, Age: <mark>30</mark>"
@@ -223,7 +232,7 @@ const html = highlight`Name: ${name}, Age: ${age}`;
### 5. Enhanced Object Literals
```javascript
const name = 'John';
const name = "John";
const age = 30;
// Shorthand property names
@@ -236,28 +245,31 @@ const calculator = {
},
subtract(a, b) {
return a - b;
}
},
};
// Computed property names
const field = 'email';
const field = "email";
const user = {
name: 'John',
[field]: 'john@example.com',
name: "John",
[field]: "john@example.com",
[`get${field.charAt(0).toUpperCase()}${field.slice(1)}`]() {
return this[field];
}
},
};
// Dynamic property creation
const createUser = (name, ...props) => {
return props.reduce((user, [key, value]) => ({
...user,
[key]: value
}), { name });
return props.reduce(
(user, [key, value]) => ({
...user,
[key]: value,
}),
{ name },
);
};
const user = createUser('John', ['age', 30], ['email', 'john@example.com']);
const user = createUser("John", ["age", 30], ["email", "john@example.com"]);
```
## Asynchronous Patterns
@@ -265,15 +277,16 @@ const user = createUser('John', ['age', 30], ['email', 'john@example.com']);
### 1. Promises
**Creating and Using Promises:**
```javascript
// Creating a promise
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: 'John' });
resolve({ id, name: "John" });
} else {
reject(new Error('Invalid ID'));
reject(new Error("Invalid ID"));
}
}, 1000);
});
@@ -281,57 +294,54 @@ const fetchUser = (id) => {
// Using promises
fetchUser(1)
.then(user => console.log(user))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
.then((user) => console.log(user))
.catch((error) => console.error(error))
.finally(() => console.log("Done"));
// Chaining promises
fetchUser(1)
.then(user => fetchUserPosts(user.id))
.then(posts => processPosts(posts))
.then(result => console.log(result))
.catch(error => console.error(error));
.then((user) => fetchUserPosts(user.id))
.then((posts) => processPosts(posts))
.then((result) => console.log(result))
.catch((error) => console.error(error));
```
**Promise Combinators:**
```javascript
// Promise.all - Wait for all promises
const promises = [
fetchUser(1),
fetchUser(2),
fetchUser(3)
];
const promises = [fetchUser(1), fetchUser(2), fetchUser(3)];
Promise.all(promises)
.then(users => console.log(users))
.catch(error => console.error('At least one failed:', error));
.then((users) => console.log(users))
.catch((error) => console.error("At least one failed:", error));
// Promise.allSettled - Wait for all, regardless of outcome
Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Error:', result.reason);
}
});
Promise.allSettled(promises).then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.log("Error:", result.reason);
}
});
});
// Promise.race - First to complete
Promise.race(promises)
.then(winner => console.log('First:', winner))
.catch(error => console.error(error));
.then((winner) => console.log("First:", winner))
.catch((error) => console.error(error));
// Promise.any - First to succeed
Promise.any(promises)
.then(first => console.log('First success:', first))
.catch(error => console.error('All failed:', error));
.then((first) => console.log("First success:", first))
.catch((error) => console.error("All failed:", error));
```
### 2. Async/Await
**Basic Usage:**
```javascript
// Async function always returns a Promise
async function fetchUser(id) {
@@ -347,28 +357,26 @@ async function getUserData(id) {
const posts = await fetchUserPosts(user.id);
return { user, posts };
} catch (error) {
console.error('Error fetching data:', error);
console.error("Error fetching data:", error);
throw error;
}
}
// Sequential vs Parallel execution
async function sequential() {
const user1 = await fetchUser(1); // Wait
const user2 = await fetchUser(2); // Then wait
const user1 = await fetchUser(1); // Wait
const user2 = await fetchUser(2); // Then wait
return [user1, user2];
}
async function parallel() {
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2)
]);
const [user1, user2] = await Promise.all([fetchUser(1), fetchUser(2)]);
return [user1, user2];
}
```
**Advanced Patterns:**
```javascript
// Async IIFE
(async () => {
@@ -385,7 +393,7 @@ async function processUsers(userIds) {
}
// Top-level await (ES2022)
const config = await fetch('/config.json').then(r => r.json());
const config = await fetch("/config.json").then((r) => r.json());
// Retry logic
async function fetchWithRetry(url, retries = 3) {
@@ -394,7 +402,7 @@ async function fetchWithRetry(url, retries = 3) {
return await fetch(url);
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
@@ -402,7 +410,7 @@ async function fetchWithRetry(url, retries = 3) {
// Timeout wrapper
async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
setTimeout(() => reject(new Error("Timeout")), ms),
);
return Promise.race([promise, timeout]);
}
@@ -413,20 +421,21 @@ async function withTimeout(promise, ms) {
### 1. Array Methods
**Map, Filter, Reduce:**
```javascript
const users = [
{ id: 1, name: 'John', age: 30, active: true },
{ id: 2, name: 'Jane', age: 25, active: false },
{ id: 3, name: 'Bob', age: 35, active: true }
{ id: 1, name: "John", age: 30, active: true },
{ id: 2, name: "Jane", age: 25, active: false },
{ id: 3, name: "Bob", age: 35, active: true },
];
// Map - Transform array
const names = users.map(user => user.name);
const upperNames = users.map(user => user.name.toUpperCase());
const names = users.map((user) => user.name);
const upperNames = users.map((user) => user.name.toUpperCase());
// Filter - Select elements
const activeUsers = users.filter(user => user.active);
const adults = users.filter(user => user.age >= 18);
const activeUsers = users.filter((user) => user.active);
const adults = users.filter((user) => user.age >= 18);
// Reduce - Aggregate data
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
@@ -434,44 +443,45 @@ const avgAge = totalAge / users.length;
// Group by property
const byActive = users.reduce((groups, user) => {
const key = user.active ? 'active' : 'inactive';
const key = user.active ? "active" : "inactive";
return {
...groups,
[key]: [...(groups[key] || []), user]
[key]: [...(groups[key] || []), user],
};
}, {});
// Chaining methods
const result = users
.filter(user => user.active)
.map(user => user.name)
.filter((user) => user.active)
.map((user) => user.name)
.sort()
.join(', ');
.join(", ");
```
**Advanced Array Methods:**
```javascript
// Find - First matching element
const user = users.find(u => u.id === 2);
const user = users.find((u) => u.id === 2);
// FindIndex - Index of first match
const index = users.findIndex(u => u.name === 'Jane');
const index = users.findIndex((u) => u.name === "Jane");
// Some - At least one matches
const hasActive = users.some(u => u.active);
const hasActive = users.some((u) => u.active);
// Every - All match
const allAdults = users.every(u => u.age >= 18);
const allAdults = users.every((u) => u.age >= 18);
// FlatMap - Map and flatten
const userTags = [
{ name: 'John', tags: ['admin', 'user'] },
{ name: 'Jane', tags: ['user'] }
{ name: "John", tags: ["admin", "user"] },
{ name: "Jane", tags: ["user"] },
];
const allTags = userTags.flatMap(u => u.tags);
const allTags = userTags.flatMap((u) => u.tags);
// From - Create array from iterable
const str = 'hello';
const str = "hello";
const chars = Array.from(str);
const numbers = Array.from({ length: 5 }, (_, i) => i + 1);
@@ -482,6 +492,7 @@ const arr = Array.of(1, 2, 3);
### 2. Higher-Order Functions
**Functions as Arguments:**
```javascript
// Custom forEach
function forEach(array, callback) {
@@ -512,14 +523,15 @@ function filter(array, predicate) {
```
**Functions Returning Functions:**
```javascript
// Currying
const multiply = a => b => a * b;
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Partial application
function partial(fn, ...args) {
@@ -528,7 +540,7 @@ function partial(fn, ...args) {
const add = (a, b, c) => a + b + c;
const add5 = partial(add, 5);
console.log(add5(3, 2)); // 10
console.log(add5(3, 2)); // 10
// Memoization
function memoize(fn) {
@@ -554,34 +566,38 @@ const fibonacci = memoize((n) => {
```javascript
// Function composition
const compose = (...fns) => x =>
fns.reduceRight((acc, fn) => fn(acc), x);
const compose =
(...fns) =>
(x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x =>
fns.reduce((acc, fn) => fn(acc), x);
const pipe =
(...fns) =>
(x) =>
fns.reduce((acc, fn) => fn(acc), x);
// Example usage
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const addOne = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;
const composed = compose(square, double, addOne);
console.log(composed(3)); // ((3 + 1) * 2)^2 = 64
console.log(composed(3)); // ((3 + 1) * 2)^2 = 64
const piped = pipe(addOne, double, square);
console.log(piped(3)); // ((3 + 1) * 2)^2 = 64
console.log(piped(3)); // ((3 + 1) * 2)^2 = 64
// Practical example
const processUser = pipe(
user => ({ ...user, name: user.name.trim() }),
user => ({ ...user, email: user.email.toLowerCase() }),
user => ({ ...user, age: parseInt(user.age) })
(user) => ({ ...user, name: user.name.trim() }),
(user) => ({ ...user, email: user.email.toLowerCase() }),
(user) => ({ ...user, age: parseInt(user.age) }),
);
const user = processUser({
name: ' John ',
email: 'JOHN@EXAMPLE.COM',
age: '30'
name: " John ",
email: "JOHN@EXAMPLE.COM",
age: "30",
});
```
@@ -600,7 +616,7 @@ function addItemPure(cart, item) {
return {
...cart,
items: [...cart.items, item],
total: cart.total + item.price
total: cart.total + item.price,
};
}
@@ -611,28 +627,28 @@ const numbers = [1, 2, 3, 4, 5];
const withSix = [...numbers, 6];
// Remove from array
const withoutThree = numbers.filter(n => n !== 3);
const withoutThree = numbers.filter((n) => n !== 3);
// Update array element
const doubled = numbers.map(n => n === 3 ? n * 2 : n);
const doubled = numbers.map((n) => (n === 3 ? n * 2 : n));
// Immutable object operations
const user = { name: 'John', age: 30 };
const user = { name: "John", age: 30 };
// Update property
const olderUser = { ...user, age: 31 };
// Add property
const withEmail = { ...user, email: 'john@example.com' };
const withEmail = { ...user, email: "john@example.com" };
// Remove property
const { age, ...withoutAge } = user;
// Deep cloning (simple approach)
const deepClone = obj => JSON.parse(JSON.stringify(obj));
const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
// Better deep cloning
const structuredClone = obj => globalThis.structuredClone(obj);
const structuredClone = (obj) => globalThis.structuredClone(obj);
```
## Modern Class Features
@@ -716,21 +732,21 @@ export default function multiply(a, b) {
// Importing
// app.js
import multiply, { PI, add, Calculator } from './math.js';
import multiply, { PI, add, Calculator } from "./math.js";
// Rename imports
import { add as sum } from './math.js';
import { add as sum } from "./math.js";
// Import all
import * as Math from './math.js';
import * as Math from "./math.js";
// Dynamic imports
const module = await import('./math.js');
const { add } = await import('./math.js');
const module = await import("./math.js");
const { add } = await import("./math.js");
// Conditional loading
if (condition) {
const module = await import('./feature.js');
const module = await import("./feature.js");
module.init();
}
```
@@ -754,13 +770,13 @@ const range = {
} else {
return { done: true };
}
}
},
};
}
},
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
console.log(num); // 1, 2, 3, 4, 5
}
// Generator function
@@ -795,7 +811,7 @@ async function* fetchPages(url) {
}
}
for await (const page of fetchPages('/api/users')) {
for await (const page of fetchPages("/api/users")) {
console.log(page);
}
```
@@ -804,9 +820,9 @@ for await (const page of fetchPages('/api/users')) {
```javascript
// Optional chaining
const user = { name: 'John', address: { city: 'NYC' } };
const user = { name: "John", address: { city: "NYC" } };
const city = user?.address?.city;
const zipCode = user?.address?.zipCode; // undefined
const zipCode = user?.address?.zipCode; // undefined
// Function call
const result = obj.method?.();
@@ -815,21 +831,21 @@ const result = obj.method?.();
const first = arr?.[0];
// Nullish coalescing
const value = null ?? 'default'; // 'default'
const value = undefined ?? 'default'; // 'default'
const value = 0 ?? 'default'; // 0 (not 'default')
const value = '' ?? 'default'; // '' (not 'default')
const value = null ?? "default"; // 'default'
const value = undefined ?? "default"; // 'default'
const value = 0 ?? "default"; // 0 (not 'default')
const value = "" ?? "default"; // '' (not 'default')
// Logical assignment
let a = null;
a ??= 'default'; // a = 'default'
a ??= "default"; // a = 'default'
let b = 5;
b ??= 10; // b = 5 (unchanged)
b ??= 10; // b = 5 (unchanged)
let obj = { count: 0 };
obj.count ||= 1; // obj.count = 1
obj.count &&= 2; // obj.count = 2
obj.count ||= 1; // obj.count = 1
obj.count &&= 2; // obj.count = 2
```
## Performance Optimization
@@ -853,7 +869,7 @@ function throttle(fn, limit) {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
setTimeout(() => (inThrottle = false), limit);
}
};
}
@@ -869,8 +885,8 @@ function* lazyMap(iterable, transform) {
// Use only what you need
const numbers = [1, 2, 3, 4, 5];
const doubled = lazyMap(numbers, x => x * 2);
const first = doubled.next().value; // Only computes first value
const doubled = lazyMap(numbers, (x) => x * 2);
const first = doubled.next().value; // Only computes first value
```
## Best Practices

View File

@@ -23,22 +23,23 @@ Comprehensive guidance for building scalable, maintainable, and production-ready
### Express.js - Minimalist Framework
**Basic Setup:**
```typescript
import express, { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
import express, { Request, Response, NextFunction } from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
app.use(compression());
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
// Request logging
app.use((req: Request, res: Response, next: NextFunction) => {
@@ -55,20 +56,21 @@ app.listen(PORT, () => {
### Fastify - High Performance Framework
**Basic Setup:**
```typescript
import Fastify from 'fastify';
import helmet from '@fastify/helmet';
import cors from '@fastify/cors';
import compress from '@fastify/compress';
import Fastify from "fastify";
import helmet from "@fastify/helmet";
import cors from "@fastify/cors";
import compress from "@fastify/compress";
const fastify = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
level: process.env.LOG_LEVEL || "info",
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
}
target: "pino-pretty",
options: { colorize: true },
},
},
});
// Plugins
@@ -80,23 +82,27 @@ await fastify.register(compress);
fastify.post<{
Body: { name: string; email: string };
Reply: { id: string; name: string };
}>('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' }
}
}
}
}, async (request, reply) => {
const { name, email } = request.body;
return { id: '123', name };
});
}>(
"/users",
{
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
},
},
},
},
async (request, reply) => {
const { name, email } = request.body;
return { id: "123", name };
},
);
await fastify.listen({ port: 3000, host: '0.0.0.0' });
await fastify.listen({ port: 3000, host: "0.0.0.0" });
```
## Architectural Patterns
@@ -104,6 +110,7 @@ await fastify.listen({ port: 3000, host: '0.0.0.0' });
### Pattern 1: Layered Architecture
**Structure:**
```
src/
├── controllers/ # Handle HTTP requests/responses
@@ -118,11 +125,12 @@ src/
```
**Controller Layer:**
```typescript
// controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/user.service';
import { CreateUserDTO, UpdateUserDTO } from '../types/user.types';
import { Request, Response, NextFunction } from "express";
import { UserService } from "../services/user.service";
import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";
export class UserController {
constructor(private userService: UserService) {}
@@ -171,12 +179,13 @@ export class UserController {
```
**Service Layer:**
```typescript
// services/user.service.ts
import { UserRepository } from '../repositories/user.repository';
import { CreateUserDTO, UpdateUserDTO, User } from '../types/user.types';
import { NotFoundError, ValidationError } from '../utils/errors';
import bcrypt from 'bcrypt';
import { UserRepository } from "../repositories/user.repository";
import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";
import { NotFoundError, ValidationError } from "../utils/errors";
import bcrypt from "bcrypt";
export class UserService {
constructor(private userRepository: UserRepository) {}
@@ -185,7 +194,7 @@ export class UserService {
// Validation
const existingUser = await this.userRepository.findByEmail(userData.email);
if (existingUser) {
throw new ValidationError('Email already exists');
throw new ValidationError("Email already exists");
}
// Hash password
@@ -194,7 +203,7 @@ export class UserService {
// Create user
const user = await this.userRepository.create({
...userData,
password: hashedPassword
password: hashedPassword,
});
// Remove password from response
@@ -205,7 +214,7 @@ export class UserService {
async getUserById(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new NotFoundError('User not found');
throw new NotFoundError("User not found");
}
const { password, ...userWithoutPassword } = user;
return userWithoutPassword as User;
@@ -214,7 +223,7 @@ export class UserService {
async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {
const user = await this.userRepository.update(id, updates);
if (!user) {
throw new NotFoundError('User not found');
throw new NotFoundError("User not found");
}
const { password, ...userWithoutPassword } = user;
return userWithoutPassword as User;
@@ -223,22 +232,25 @@ export class UserService {
async deleteUser(id: string): Promise<void> {
const deleted = await this.userRepository.delete(id);
if (!deleted) {
throw new NotFoundError('User not found');
throw new NotFoundError("User not found");
}
}
}
```
**Repository Layer:**
```typescript
// repositories/user.repository.ts
import { Pool } from 'pg';
import { CreateUserDTO, UpdateUserDTO, UserEntity } from '../types/user.types';
import { Pool } from "pg";
import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";
export class UserRepository {
constructor(private db: Pool) {}
async create(userData: CreateUserDTO & { password: string }): Promise<UserEntity> {
async create(
userData: CreateUserDTO & { password: string },
): Promise<UserEntity> {
const query = `
INSERT INTO users (name, email, password)
VALUES ($1, $2, $3)
@@ -247,19 +259,19 @@ export class UserRepository {
const { rows } = await this.db.query(query, [
userData.name,
userData.email,
userData.password
userData.password,
]);
return rows[0];
}
async findById(id: string): Promise<UserEntity | null> {
const query = 'SELECT * FROM users WHERE id = $1';
const query = "SELECT * FROM users WHERE id = $1";
const { rows } = await this.db.query(query, [id]);
return rows[0] || null;
}
async findByEmail(email: string): Promise<UserEntity | null> {
const query = 'SELECT * FROM users WHERE email = $1';
const query = "SELECT * FROM users WHERE email = $1";
const { rows } = await this.db.query(query, [email]);
return rows[0] || null;
}
@@ -270,7 +282,7 @@ export class UserRepository {
const setClause = fields
.map((field, idx) => `${field} = $${idx + 2}`)
.join(', ');
.join(", ");
const query = `
UPDATE users
@@ -284,7 +296,7 @@ export class UserRepository {
}
async delete(id: string): Promise<boolean> {
const query = 'DELETE FROM users WHERE id = $1';
const query = "DELETE FROM users WHERE id = $1";
const { rowCount } = await this.db.query(query, [id]);
return rowCount > 0;
}
@@ -294,13 +306,14 @@ export class UserRepository {
### Pattern 2: Dependency Injection
**DI Container:**
```typescript
// di-container.ts
import { Pool } from 'pg';
import { UserRepository } from './repositories/user.repository';
import { UserService } from './services/user.service';
import { UserController } from './controllers/user.controller';
import { AuthService } from './services/auth.service';
import { Pool } from "pg";
import { UserRepository } from "./repositories/user.repository";
import { UserService } from "./services/user.service";
import { UserController } from "./controllers/user.controller";
import { AuthService } from "./services/auth.service";
class Container {
private instances = new Map<string, any>();
@@ -331,31 +344,39 @@ class Container {
export const container = new Container();
// Register dependencies
container.singleton('db', () => new Pool({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}));
container.singleton('userRepository', () =>
new UserRepository(container.resolve('db'))
container.singleton(
"db",
() =>
new Pool({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}),
);
container.singleton('userService', () =>
new UserService(container.resolve('userRepository'))
container.singleton(
"userRepository",
() => new UserRepository(container.resolve("db")),
);
container.register('userController', () =>
new UserController(container.resolve('userService'))
container.singleton(
"userService",
() => new UserService(container.resolve("userRepository")),
);
container.singleton('authService', () =>
new AuthService(container.resolve('userRepository'))
container.register(
"userController",
() => new UserController(container.resolve("userService")),
);
container.singleton(
"authService",
() => new AuthService(container.resolve("userRepository")),
);
```
@@ -365,9 +386,9 @@ container.singleton('authService', () =>
```typescript
// middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { UnauthorizedError } from '../utils/errors';
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { UnauthorizedError } from "../utils/errors";
interface JWTPayload {
userId: string;
@@ -385,40 +406,35 @@ declare global {
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction
next: NextFunction,
) => {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
throw new UnauthorizedError('No token provided');
throw new UnauthorizedError("No token provided");
}
const payload = jwt.verify(
token,
process.env.JWT_SECRET!
) as JWTPayload;
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
req.user = payload;
next();
} catch (error) {
next(new UnauthorizedError('Invalid token'));
next(new UnauthorizedError("Invalid token"));
}
};
export const authorize = (...roles: string[]) => {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return next(new UnauthorizedError('Not authenticated'));
return next(new UnauthorizedError("Not authenticated"));
}
// Check if user has required role
const hasRole = roles.some(role =>
req.user?.roles?.includes(role)
);
const hasRole = roles.some((role) => req.user?.roles?.includes(role));
if (!hasRole) {
return next(new UnauthorizedError('Insufficient permissions'));
return next(new UnauthorizedError("Insufficient permissions"));
}
next();
@@ -430,9 +446,9 @@ export const authorize = (...roles: string[]) => {
```typescript
// middleware/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AnyZodObject, ZodError } from 'zod';
import { ValidationError } from '../utils/errors';
import { Request, Response, NextFunction } from "express";
import { AnyZodObject, ZodError } from "zod";
import { ValidationError } from "../utils/errors";
export const validate = (schema: AnyZodObject) => {
return async (req: Request, res: Response, next: NextFunction) => {
@@ -440,16 +456,16 @@ export const validate = (schema: AnyZodObject) => {
await schema.parseAsync({
body: req.body,
query: req.query,
params: req.params
params: req.params,
});
next();
} catch (error) {
if (error instanceof ZodError) {
const errors = error.errors.map(err => ({
field: err.path.join('.'),
message: err.message
const errors = error.errors.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
next(new ValidationError('Validation failed', errors));
next(new ValidationError("Validation failed", errors));
} else {
next(error);
}
@@ -458,40 +474,40 @@ export const validate = (schema: AnyZodObject) => {
};
// Usage with Zod
import { z } from 'zod';
import { z } from "zod";
const createUserSchema = z.object({
body: z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(8)
})
password: z.string().min(8),
}),
});
router.post('/users', validate(createUserSchema), userController.createUser);
router.post("/users", validate(createUserSchema), userController.createUser);
```
### Rate Limiting Middleware
```typescript
// middleware/rate-limit.middleware.ts
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379')
port: parseInt(process.env.REDIS_PORT || "6379"),
});
export const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:',
prefix: "rl:",
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later',
message: "Too many requests from this IP, please try again later",
standardHeaders: true,
legacyHeaders: false,
});
@@ -499,7 +515,7 @@ export const apiLimiter = rateLimit({
export const authLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:auth:',
prefix: "rl:auth:",
}),
windowMs: 15 * 60 * 1000,
max: 5, // Stricter limit for auth endpoints
@@ -511,34 +527,34 @@ export const authLimiter = rateLimit({
```typescript
// middleware/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
import pino from 'pino';
import { Request, Response, NextFunction } from "express";
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
level: process.env.LOG_LEVEL || "info",
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
target: "pino-pretty",
options: { colorize: true },
},
});
export const requestLogger = (
req: Request,
res: Response,
next: NextFunction
next: NextFunction,
) => {
const start = Date.now();
// Log response when finished
res.on('finish', () => {
res.on("finish", () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
userAgent: req.headers['user-agent'],
ip: req.ip
userAgent: req.headers["user-agent"],
ip: req.ip,
});
});
@@ -558,7 +574,7 @@ export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public isOperational: boolean = true
public isOperational: boolean = true,
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
@@ -567,25 +583,28 @@ export class AppError extends Error {
}
export class ValidationError extends AppError {
constructor(message: string, public errors?: any[]) {
constructor(
message: string,
public errors?: any[],
) {
super(message, 400);
}
}
export class NotFoundError extends AppError {
constructor(message: string = 'Resource not found') {
constructor(message: string = "Resource not found") {
super(message, 404);
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = 'Unauthorized') {
constructor(message: string = "Unauthorized") {
super(message, 401);
}
}
export class ForbiddenError extends AppError {
constructor(message: string = 'Forbidden') {
constructor(message: string = "Forbidden") {
super(message, 403);
}
}
@@ -601,21 +620,21 @@ export class ConflictError extends AppError {
```typescript
// middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
import { logger } from './logger.middleware';
import { Request, Response, NextFunction } from "express";
import { AppError } from "../utils/errors";
import { logger } from "./logger.middleware";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
next: NextFunction,
) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
status: 'error',
status: "error",
message: err.message,
...(err instanceof ValidationError && { errors: err.errors })
...(err instanceof ValidationError && { errors: err.errors }),
});
}
@@ -624,23 +643,24 @@ export const errorHandler = (
error: err.message,
stack: err.stack,
url: req.url,
method: req.method
method: req.method,
});
// Don't leak error details in production
const message = process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message;
const message =
process.env.NODE_ENV === "production"
? "Internal server error"
: err.message;
res.status(500).json({
status: 'error',
message
status: "error",
message,
});
};
// Async error wrapper
export const asyncHandler = (
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
@@ -654,11 +674,11 @@ export const asyncHandler = (
```typescript
// config/database.ts
import { Pool, PoolConfig } from 'pg';
import { Pool, PoolConfig } from "pg";
const poolConfig: PoolConfig = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
@@ -670,19 +690,19 @@ const poolConfig: PoolConfig = {
export const pool = new Pool(poolConfig);
// Test connection
pool.on('connect', () => {
console.log('Database connected');
pool.on("connect", () => {
console.log("Database connected");
});
pool.on('error', (err) => {
console.error('Unexpected database error', err);
pool.on("error", (err) => {
console.error("Unexpected database error", err);
process.exit(-1);
});
// Graceful shutdown
export const closeDatabase = async () => {
await pool.end();
console.log('Database connection closed');
console.log("Database connection closed");
};
```
@@ -690,7 +710,7 @@ export const closeDatabase = async () => {
```typescript
// config/mongoose.ts
import mongoose from 'mongoose';
import mongoose from "mongoose";
const connectDB = async () => {
try {
@@ -700,25 +720,25 @@ const connectDB = async () => {
socketTimeoutMS: 45000,
});
console.log('MongoDB connected');
console.log("MongoDB connected");
} catch (error) {
console.error('MongoDB connection error:', error);
console.error("MongoDB connection error:", error);
process.exit(1);
}
};
mongoose.connection.on('disconnected', () => {
console.log('MongoDB disconnected');
mongoose.connection.on("disconnected", () => {
console.log("MongoDB disconnected");
});
mongoose.connection.on('error', (err) => {
console.error('MongoDB error:', err);
mongoose.connection.on("error", (err) => {
console.error("MongoDB error:", err);
});
export { connectDB };
// Model example
import { Schema, model, Document } from 'mongoose';
import { Schema, model, Document } from "mongoose";
interface IUser extends Document {
name: string;
@@ -728,25 +748,28 @@ interface IUser extends Document {
updatedAt: Date;
}
const userSchema = new Schema<IUser>({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
}, {
timestamps: true
});
const userSchema = new Schema<IUser>(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
},
{
timestamps: true,
},
);
// Indexes
userSchema.index({ email: 1 });
export const User = model<IUser>('User', userSchema);
export const User = model<IUser>("User", userSchema);
```
### Transaction Pattern
```typescript
// services/order.service.ts
import { Pool } from 'pg';
import { Pool } from "pg";
export class OrderService {
constructor(private db: Pool) {}
@@ -755,33 +778,33 @@ export class OrderService {
const client = await this.db.connect();
try {
await client.query('BEGIN');
await client.query("BEGIN");
// Create order
const orderResult = await client.query(
'INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id',
[userId, calculateTotal(items)]
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
[userId, calculateTotal(items)],
);
const orderId = orderResult.rows[0].id;
// Create order items
for (const item of items) {
await client.query(
'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)',
[orderId, item.productId, item.quantity, item.price]
"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
[orderId, item.productId, item.quantity, item.price],
);
// Update inventory
await client.query(
'UPDATE products SET stock = stock - $1 WHERE id = $2',
[item.quantity, item.productId]
"UPDATE products SET stock = stock - $1 WHERE id = $2",
[item.quantity, item.productId],
);
}
await client.query('COMMIT');
await client.query("COMMIT");
return orderId;
} catch (error) {
await client.query('ROLLBACK');
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
@@ -796,10 +819,10 @@ export class OrderService {
```typescript
// services/auth.service.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { UserRepository } from '../repositories/user.repository';
import { UnauthorizedError } from '../utils/errors';
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import { UserRepository } from "../repositories/user.repository";
import { UnauthorizedError } from "../utils/errors";
export class AuthService {
constructor(private userRepository: UserRepository) {}
@@ -808,22 +831,22 @@ export class AuthService {
const user = await this.userRepository.findByEmail(email);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
throw new UnauthorizedError("Invalid credentials");
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new UnauthorizedError('Invalid credentials');
throw new UnauthorizedError("Invalid credentials");
}
const token = this.generateToken({
userId: user.id,
email: user.email
email: user.email,
});
const refreshToken = this.generateRefreshToken({
userId: user.id
userId: user.id,
});
return {
@@ -832,8 +855,8 @@ export class AuthService {
user: {
id: user.id,
name: user.name,
email: user.email
}
email: user.email,
},
};
}
@@ -841,35 +864,35 @@ export class AuthService {
try {
const payload = jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET!
process.env.REFRESH_TOKEN_SECRET!,
) as { userId: string };
const user = await this.userRepository.findById(payload.userId);
if (!user) {
throw new UnauthorizedError('User not found');
throw new UnauthorizedError("User not found");
}
const token = this.generateToken({
userId: user.id,
email: user.email
email: user.email,
});
return { token };
} catch (error) {
throw new UnauthorizedError('Invalid refresh token');
throw new UnauthorizedError("Invalid refresh token");
}
}
private generateToken(payload: any): string {
return jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: '15m'
expiresIn: "15m",
});
}
private generateRefreshToken(payload: any): string {
return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {
expiresIn: '7d'
expiresIn: "7d",
});
}
}
@@ -879,15 +902,15 @@ export class AuthService {
```typescript
// utils/cache.ts
import Redis from 'ioredis';
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
port: parseInt(process.env.REDIS_PORT || "6379"),
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
}
},
});
export class CacheService {
@@ -922,7 +945,7 @@ export function Cacheable(ttl: number = 300) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
descriptor: PropertyDescriptor,
) {
const originalMethod = descriptor.value;
@@ -950,22 +973,27 @@ export function Cacheable(ttl: number = 300) {
```typescript
// utils/response.ts
import { Response } from 'express';
import { Response } from "express";
export class ApiResponse {
static success<T>(res: Response, data: T, message?: string, statusCode = 200) {
static success<T>(
res: Response,
data: T,
message?: string,
statusCode = 200,
) {
return res.status(statusCode).json({
status: 'success',
status: "success",
message,
data
data,
});
}
static error(res: Response, message: string, statusCode = 500, errors?: any) {
return res.status(statusCode).json({
status: 'error',
status: "error",
message,
...(errors && { errors })
...(errors && { errors }),
});
}
@@ -974,17 +1002,17 @@ export class ApiResponse {
data: T[],
page: number,
limit: number,
total: number
total: number,
) {
return res.json({
status: 'success',
status: "success",
data,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
pages: Math.ceil(total / limit),
},
});
}
}

View File

@@ -25,17 +25,19 @@ Comprehensive guidance for mastering TypeScript's advanced type system including
**Purpose:** Create reusable, type-flexible components while maintaining type safety.
**Basic Generic Function:**
```typescript
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // Type: number
const str = identity<string>("hello"); // Type: string
const auto = identity(true); // Type inferred: boolean
const num = identity<number>(42); // Type: number
const str = identity<string>("hello"); // Type: string
const auto = identity(true); // Type inferred: boolean
```
**Generic Constraints:**
```typescript
interface HasLength {
length: number;
@@ -46,22 +48,20 @@ function logLength<T extends HasLength>(item: T): T {
return item;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength({ length: 10 }); // OK: object has length
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength({ length: 10 }); // OK: object has length
// logLength(42); // Error: number has no length
```
**Multiple Type Parameters:**
```typescript
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge(
{ name: "John" },
{ age: 30 }
);
const merged = merge({ name: "John" }, { age: 30 });
// Type: { name: string } & { age: number }
```
@@ -70,14 +70,16 @@ const merged = merge(
**Purpose:** Create types that depend on conditions, enabling sophisticated type logic.
**Basic Conditional Type:**
```typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type A = IsString<string>; // true
type B = IsString<number>; // false
```
**Extracting Return Types:**
```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
@@ -90,6 +92,7 @@ type User = ReturnType<typeof getUser>;
```
**Distributive Conditional Types:**
```typescript
type ToArray<T> = T extends any ? T[] : never;
@@ -98,16 +101,21 @@ type StrOrNumArray = ToArray<string | number>;
```
**Nested Conditions:**
```typescript
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<string>; // "string"
```typescript
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<() => void>; // "function"
```
@@ -116,6 +124,7 @@ type T2 = TypeName<() => void>; // "function"
**Purpose:** Transform existing types by iterating over their properties.
**Basic Mapped Type:**
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
@@ -131,6 +140,7 @@ type ReadonlyUser = Readonly<User>;
```
**Optional Properties:**
```typescript
type Partial<T> = {
[P in keyof T]?: T[P];
@@ -141,9 +151,10 @@ type PartialUser = Partial<User>;
```
**Key Remapping:**
```typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
@@ -156,9 +167,10 @@ type PersonGetters = Getters<Person>;
```
**Filtering Properties:**
```typescript
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Mixed {
@@ -177,6 +189,7 @@ type OnlyNumbers = PickByType<Mixed, number>;
**Purpose:** Create string-based types with pattern matching and transformation.
**Basic Template Literal:**
```typescript
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
@@ -184,19 +197,20 @@ type EventHandler = `on${Capitalize<EventName>}`;
```
**String Manipulation:**
```typescript
type UppercaseGreeting = Uppercase<"hello">; // "HELLO"
type LowercaseGreeting = Lowercase<"HELLO">; // "hello"
type CapitalizedName = Capitalize<"john">; // "John"
type UppercaseGreeting = Uppercase<"hello">; // "HELLO"
type LowercaseGreeting = Lowercase<"HELLO">; // "hello"
type CapitalizedName = Capitalize<"john">; // "John"
type UncapitalizedName = Uncapitalize<"John">; // "john"
```
**Path Building:**
```typescript
type Path<T> = T extends object
? { [K in keyof T]: K extends string
? `${K}` | `${K}.${Path<T[K]>}`
: never
? {
[K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;
}[keyof T]
: never;
@@ -235,13 +249,13 @@ type UserName = Pick<User, "name" | "email">;
type UserWithoutPassword = Omit<User, "password">;
// Exclude<T, U> - Exclude types from union
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
// Extract<T, U> - Extract types from union
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
// NonNullable<T> - Exclude null and undefined
type T3 = NonNullable<string | null | undefined>; // string
type T3 = NonNullable<string | null | undefined>; // string
// Record<K, T> - Create object type with keys K and values T
type PageInfo = Record<"home" | "about", { title: string }>;
@@ -273,7 +287,7 @@ class TypedEventEmitter<T extends Record<string, any>> {
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners[event];
if (callbacks) {
callbacks.forEach(callback => callback(data));
callbacks.forEach((callback) => callback(data));
}
}
}
@@ -281,7 +295,7 @@ class TypedEventEmitter<T extends Record<string, any>> {
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("user:created", (data) => {
console.log(data.id, data.name); // Type-safe!
console.log(data.id, data.name); // Type-safe!
});
emitter.emit("user:created", { id: "1", name: "John" });
@@ -310,20 +324,19 @@ type ExtractBody<T> = T extends { body: infer B } ? B : never;
type ExtractResponse<T> = T extends { response: infer R } ? R : never;
class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
async request<
Path extends keyof Config,
Method extends keyof Config[Path]
>(
async request<Path extends keyof Config, Method extends keyof Config[Path]>(
path: Path,
method: Method,
...[options]: ExtractParams<Config[Path][Method]> extends never
? ExtractBody<Config[Path][Method]> extends never
? []
: [{ body: ExtractBody<Config[Path][Method]> }]
: [{
params: ExtractParams<Config[Path][Method]>;
body?: ExtractBody<Config[Path][Method]>;
}]
: [
{
params: ExtractParams<Config[Path][Method]>;
body?: ExtractBody<Config[Path][Method]>;
},
]
): Promise<ExtractResponse<Config[Path][Method]>> {
// Implementation here
return {} as any;
@@ -337,12 +350,12 @@ const users = await api.request("/users", "GET");
// Type: User[]
const newUser = await api.request("/users", "POST", {
body: { name: "John", email: "john@example.com" }
body: { name: "John", email: "john@example.com" },
});
// Type: User
const user = await api.request("/users/:id", "GET", {
params: { id: "123" }
params: { id: "123" },
});
// Type: User
```
@@ -372,17 +385,12 @@ type IsComplete<T, S> =
class Builder<T, S extends BuilderState<T> = {}> {
private state: S = {} as S;
set<K extends keyof T>(
key: K,
value: T[K]
): Builder<T, S & Record<K, T[K]>> {
set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
this.state[key] = value;
return this as any;
}
build(
this: IsComplete<T, S> extends true ? this : never
): T {
build(this: IsComplete<T, S> extends true ? this : never): T {
return this.state as T;
}
}
@@ -400,7 +408,7 @@ const user = builder
.set("id", "1")
.set("name", "John")
.set("email", "john@example.com")
.build(); // OK: all required fields set
.build(); // OK: all required fields set
// const incomplete = builder
// .set("id", "1")
@@ -507,24 +515,24 @@ const validator = new FormValidator<LoginForm>({
email: [
{
validate: (v) => v.includes("@"),
message: "Email must contain @"
message: "Email must contain @",
},
{
validate: (v) => v.length > 0,
message: "Email is required"
}
message: "Email is required",
},
],
password: [
{
validate: (v) => v.length >= 8,
message: "Password must be at least 8 characters"
}
]
message: "Password must be at least 8 characters",
},
],
});
const errors = validator.validate({
email: "invalid",
password: "short"
password: "short",
});
// Type: { email?: string[]; password?: string[]; } | null
```
@@ -551,10 +559,10 @@ type AsyncState<T> = Success<T> | Error | Loading;
function handleState<T>(state: AsyncState<T>): void {
switch (state.status) {
case "success":
console.log(state.data); // Type: T
console.log(state.data); // Type: T
break;
case "error":
console.log(state.error); // Type: string
console.log(state.error); // Type: string
break;
case "loading":
console.log("Loading...");
@@ -605,18 +613,18 @@ function reducer(state: State, event: Event): State {
type ElementType<T> = T extends (infer U)[] ? U : never;
type NumArray = number[];
type Num = ElementType<NumArray>; // number
type Num = ElementType<NumArray>; // number
// Extract promise type
type PromiseType<T> = T extends Promise<infer U> ? U : never;
type AsyncNum = PromiseType<Promise<number>>; // number
type AsyncNum = PromiseType<Promise<number>>; // number
// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function foo(a: string, b: number) {}
type FooParams = Parameters<typeof foo>; // [string, number]
type FooParams = Parameters<typeof foo>; // [string, number]
```
### 2. Type Guards
@@ -628,7 +636,7 @@ function isString(value: unknown): value is string {
function isArrayOf<T>(
value: unknown,
guard: (item: unknown) => item is T
guard: (item: unknown) => item is T,
): value is T[] {
return Array.isArray(value) && value.every(guard);
}
@@ -636,7 +644,7 @@ function isArrayOf<T>(
const data: unknown = ["a", "b", "c"];
if (isArrayOf(data, isString)) {
data.forEach(s => s.toUpperCase()); // Type: string[]
data.forEach((s) => s.toUpperCase()); // Type: string[]
}
```
@@ -673,15 +681,14 @@ function processValue(value: unknown) {
```typescript
// Type assertion tests
type AssertEqual<T, U> =
[T] extends [U]
? [U] extends [T]
? true
: false
: false;
type AssertEqual<T, U> = [T] extends [U]
? [U] extends [T]
? true
: false
: false;
type Test1 = AssertEqual<string, string>; // true
type Test2 = AssertEqual<string, number>; // false
type Test1 = AssertEqual<string, string>; // true
type Test2 = AssertEqual<string, number>; // false
type Test3 = AssertEqual<string | number, string>; // false
// Expect error helper