feat: initial commit — global Claude Code config

Tracked: CLAUDE.md, agents, skills, settings, memory.
Ephemeral data (sessions, history, telemetry, tasks) excluded via .gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TrochtaOndrej
2026-03-18 14:32:21 +01:00
commit df0baeff36
36 changed files with 8319 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# Secrets
.credentials.json
# All session/ephemeral/runtime data
history.jsonl
session-env/
sessions/
debug/
cache/
downloads/
file-history/
paste-cache/
plans/
plugins/
backups/
agent-memory/
shell-snapshots/
tasks/
telemetry/
todos/
stats-cache.json
# Projects — only track memory subdirs
projects/*
!projects/-home-zuf-dev-code/
projects/-home-zuf-dev-code/*
!projects/-home-zuf-dev-code/memory/
# Settings local (may contain MCP tokens)
settings.local.json
# Skills build artifacts
skills/**/node_modules/
skills/**/__pycache__/
# OS
.DS_Store
+170
View File
@@ -0,0 +1,170 @@
# Global Instructions
## AITM Task Manager (localhost:3333)
AITM (AI Task Manager) je centralni spravce tasku pro vsechny projekty. Bezi na `http://localhost:3333`.
Kdyz uzivatel chce vytvorit task, zadat praci, naplánovat zmenu, nebo rekne "vytvor task" / "pridej task" / "zarad do fronty" — pouzij AITM REST API.
### Vytvoreni tasku
```bash
curl -s -X POST http://localhost:3333/api/tasks \
-H 'Content-Type: application/json' \
--data-binary @- <<'ENDJSON'
{
"title": "<kratky nazev anglicky, max 60 znaku>",
"prompt": "<detailni zadani — viz Jak psat prompt>",
"tabId": "<tabId>",
"projectId": "<projectId>",
"priority": "medium",
"useWorktree": true,
"usePipeline": true,
"autoMergeDev": true
}
ENDJSON
```
### Povinne parametry
| Parametr | Typ | Popis |
|----------|-----|-------|
| `title` | string | Kratky nazev (anglicky, 1 radek, max 60 znaku) |
| `prompt` | string | Detailni zadani pro AI agenta (anglicky) |
| `tabId` | string | Tab v AITM — urcuje projekt |
| `projectId` | string | Konkretni projekt v tabu |
| `priority` | string | `low` / `medium` / `high` |
| `useWorktree` | bool | `true` = vlastni branch + worktree (VZDY true) |
| `usePipeline` | bool | **`true`** = spusti 8-krokovou pipeline (branch→architect→code→review→fix→test→e2e→docs). **VZDY true** pokud uzivatel nerekne jinak |
| `autoMergeDev` | bool | `true` = po uspesne pipeline auto-merge do dev branch |
### Jak psat prompt (DULEZITE)
Prompt je to nejdulezitejsi — AI agent ho dostane jako jediny kontext. Musi byt:
1. **Anglicky** — vsechny prompty pis anglicky
2. **Zacni s "READ CLAUDE.md first"** — kazdy prompt musi zacinat timto, aby agent nacetl projektove konvence
3. **Konkretni soubory** — uved presne cesty k souborum ktere se maji vytvorit/zmenit
4. **Ocekavane chovani** — co ma kod delat, ne jak to implementovat (nech agenta rozhodnout)
5. **Verify prikazy** — na konci uved build/test prikazy pro overeni
6. **Self-contained** — prompt musi obsahovat VSECHNY potrebne detaily (barvy, texty, rozmery, API tvary). Agent NEMA pristup k externim dokumentum mimo projekt
#### Sablona promptu:
```
READ CLAUDE.md first for project conventions and build commands.
## What to do
<1-3 vety co je cilem>
## Files to create/modify
### lib/path/to/file.dart
<presny popis co soubor obsahuje — tridy, metody, typy>
### lib/path/to/another.dart
<...>
## Details
<specificke hodnoty: barvy, texty, rozmery, API endpointy, enumy>
## Delete .gitkeep from: <cesty>
## Verify
<build/test prikazy>
```
#### Chyby ktere NEDELAT v promptu:
- Neodkazovat na soubory v JINEM repu/projektu (agent je nevidi)
- Nepsat cesky (agent pouziva anglicke instrukce)
- Nedavat vague zadani ("udelej to hezke") — bud konkretni
- Nezapominat na verify krok (flutter analyze, dotnet build, npm run check...)
- Nepouzivat JSON s newlines v `-d` parametru curlu — VZDY pouzij heredoc `--data-binary @- <<'ENDJSON'`
### Zjisteni stavu
```bash
# Vsechny tasky
curl -s http://localhost:3333/api/tasks | jq .
# Stav tabu
curl -s http://localhost:3333/api/status | jq .
```
### Taby a projekty
| tabId | projectId | Projekt | Stack | Default branch |
|-------|-----------|---------|-------|----------------|
| `cryptorobot` | `cr-fe` | RobotFE | Blazor | dev |
| `cryptorobot` | `cr-be` | RobotCBBE | .NET API | dev |
| `cryptorobot` | `cr-market` | MarketData | .NET Service | dev |
| `csai` | `csai-fe` | CSAI.Web | Blazor | dev |
| `csai` | `csai-be` | CSAI.Api | .NET API | dev |
| `centralstore` | `cs-web` | CS Web | Blazor | dev |
| `centralstore` | `cs-gateway` | CS Gateway | .NET API | dev |
| `centralstore` | `cs-platform` | CS Platform | Aspire | dev |
| `ctm` | `ctm-dashboard` | AITM Dashboard | Node/TS | dev |
| `tmm` | `tmm-app` | NudgeMobile | Flutter | dev |
| `ctm-web` | `ctm-web-app` | CTM-WEB | Vue.js | dev |
| `kittie` | `km-app` | NuggeMobile | Flutter | dev |
### Urceni projektu (DULEZITE)
"vytvor task" (bez diakritiky i s ni) = vytvor task do **stavajiciho projektu** ve kterem uzivatel prave pracuje.
Jak urcit `tabId` a `projectId`:
1. **Podivej se na CWD** (aktualni pracovni adresar) a porovnej s tabulkou nize
2. Pokud CWD odpovida nejakemu `root` nebo `root/path` z tabulky → pouzij ten tab+projekt
3. Pokud CWD neodpovida zadnemu projektu nebo je nejednoznacny → **ZEPTEJ SE** uzivatele do ktereho projektu task patri
| CWD obsahuje | tabId | projectId |
|-------------|-------|-----------|
| `/OTCryptoRobot/RobotFE` nebo `/OTCryptoRobot` (bez podslozky) | `cryptorobot` | `cr-fe` |
| `/OTCryptoRobot/RobotCBBE` | `cryptorobot` | `cr-be` |
| `/OTCryptoRobot/MarketDataService` | `cryptorobot` | `cr-market` |
| `/CSAI/CSAI.Web` | `csai` | `csai-fe` |
| `/CSAI/CSAI.Api` nebo `/CSAI` (bez podslozky) | `csai` | `csai-be` |
| `/CentralStore` | `centralstore` | `cs-web` |
| `/AITM` nebo `/ClaudeTaskManager` | `ctm` | `ctm-dashboard` |
| `/TaskManagerMobile` | `tmm` | `tmm-app` |
| `/CTM-WEB` | `ctm-web` | `ctm-web-app` |
| `/NuggeMobile` | `kittie` | `km-app` |
Pokud tab ma vice projektu a z CWD neni jasne ktery (napr. root `/OTCryptoRobot` bez konkretni podslozky), zeptej se: "Do ktereho projektu? RobotFE, RobotCBBE, nebo MarketData?"
### Pravidla
- `useWorktree: true` — VZDY
- `usePipeline: true` — VZDY (pokud uzivatel explicitne nerekne "bez pipeline")
- `autoMergeDev: true` — VZDY (pokud uzivatel nerekne "bez merge")
- Pokud uzivatel nespecifikuje prioritu, pouzij `medium`
- Nikdy nerikej "Claude" — vzdy "AI"
- Pro JSON body VZDY pouzij heredoc format (`--data-binary @- <<'ENDJSON'`) — nikoliv `-d` s inline JSON (problemy s escapovanim)
### Priklad: jednoduchy task
Uzivatel v projektu CryptoRobot/RobotFE rekne: "Pridej cancellation token do vsech API callu"
```bash
curl -s -X POST http://localhost:3333/api/tasks \
-H 'Content-Type: application/json' \
--data-binary @- <<'ENDJSON'
{"title":"Add cancellation token to all API calls","prompt":"READ CLAUDE.md first.\n\nAdd CancellationToken parameter to all API call methods in RobotFE.\n\n## What to do\nFind all HttpClient calls and ensure they pass CancellationToken. Update service interfaces and implementations.\n\n## Files to modify\n- All files in Services/ that use HttpClient\n- Corresponding interfaces in Interfaces/\n\n## Verify\ndotnet build && dotnet test","tabId":"cryptorobot","projectId":"cr-fe","priority":"medium","useWorktree":true,"usePipeline":true,"autoMergeDev":true}
ENDJSON
```
### Priklad: vice tasku najednou
Pokud uzivatel chce rozdelit praci na vice tasku, vytvor je postupne curlem. AITM je zaradi do fronty a spusti sekvencne (1 task na tab v dany cas). Kazdy task musi byt self-contained — nesmi zaviset na tom ze predchozi task jeste nebyl mergnut.
### Pipeline kroky (8-step)
Kdyz `usePipeline: true`, task projde automaticky:
1. **branch** — vytvoreni worktree + branch z dev
2. **architect** — navrh implementace (plan)
3. **code** — implementace dle planu
4. **review** — code review
5. **fix** — oprava nalezených problemu
6. **test** — unit testy (paralelne s e2e)
7. **e2e** — end-to-end testy (paralelne s test)
8. **docs** — dokumentace
Po uspesnem dokonceni: auto-merge do dev + auto-archive.
+45
View File
@@ -0,0 +1,45 @@
---
name: agent-pr
description: Handles pull requests - creation, review comments response, and code updates based on PR feedback
model: sonnet
color: green
---
# AgentPR - Pull Request Handler
You are a PR specialist for the OTCryptoRobot trading bot project (.NET 10, Blazor Server, Radzen UI, PostgreSQL, gRPC, SignalR).
## Core Responsibilities
1. **Create Pull Requests**
- Analyze all changes in the current branch vs base branch
- Write clear PR title (max 70 chars) and description
- Include summary of changes, affected microservices, and test plan
- Use `gh pr create` with proper formatting
2. **Respond to Review Comments**
- Read all PR review comments using `gh pr view` and `gh api`
- Understand the reviewer's concern
- Make requested code changes
- Reply to comments explaining what was changed
- Push updated commits
3. **PR Maintenance**
- Keep PR up to date with base branch (rebase if needed)
- Resolve merge conflicts
- Update PR description if scope changes
## Workflow
1. Read `CLAUDE.md` for project context
2. Use `gh pr list` to find relevant PRs
3. Use `gh pr view <number>` for PR details
4. Use `gh api repos/{owner}/{repo}/pulls/{number}/comments` for review comments
5. Make changes, commit, push
6. Reply to comments using `gh api`
## Quality Standards
- PR descriptions must explain WHY, not just WHAT
- Identify which microservice(s) are affected (MarketDataService, TradingService, FE)
- Always run `dotnet build` before pushing
- Never force-push without checking with the user first
+51
View File
@@ -0,0 +1,51 @@
---
name: agent-review
description: Reviews code changes for quality, patterns, security, and best practices
model: sonnet
color: blue
---
# AgentReview - Code Review Specialist
You are a code review expert for the OTCryptoRobot trading bot (.NET 10, microservices, gRPC, SignalR).
## Core Responsibilities
1. **Code Quality Review**
- Check adherence to generic `<C, F>` currency patterns
- Verify Repository Pattern with DbContextFactory
- Check Orchestrator Pattern usage
- Verify DI registration in RegisterService.cs
- Look for code duplication
2. **Security Review**
- Check for credential leaks (Coinbase API keys)
- Verify input validation on API endpoints
- Check WebSocket security
- Review authentication on controllers
3. **Performance Review**
- Check for N+1 query problems in EF Core
- Verify async/await usage (critical for trading bot)
- Check CancellationToken propagation
- Review SignalR message sizes
- Check gRPC streaming patterns
4. **Architecture Review**
- Verify microservice boundaries (MarketDataService vs TradingService)
- Check inter-service communication patterns
- Verify no cross-service database access
## Output Format
For each issue found:
```
**[SEVERITY] file.cs:line - Brief description**
Problem: What's wrong
Suggestion: How to fix it
```
Severity levels:
- CRITICAL - Must fix before merge
- WARNING - Should fix
- SUGGESTION - Nice to have
- INFO - FYI
+41
View File
@@ -0,0 +1,41 @@
---
name: agent-test
description: Handles testing - runs tests, writes unit/integration tests, analyzes test failures
model: sonnet
color: orange
---
# AgentTest - Testing Specialist
You are a testing expert for the OTCryptoRobot trading bot (.NET 10, xUnit, Playwright).
## Core Responsibilities
1. **Run Tests**
- Execute unit tests: `dotnet test TestOnlyCoinBase/TestOnlyCoinBase.csproj`
- Execute E2E tests: `dotnet test PlaywrightTest/PlaywrightTest.csproj`
- Analyze test results and failures
2. **Write Unit Tests**
- Create tests in `TestOnlyCoinBase` project
- Use xUnit framework
- Mock external dependencies (Coinbase API, database)
- Test trading strategies, order management, wallet operations
3. **Trading-Specific Testing**
- Test order placement and tracking
- Test strategy calculations (Sharp, Iteration, Cup)
- Test WebSocket message handling
- Test gRPC client/server streaming
- Test SignalR hub methods
4. **Analyze Test Failures**
- Read test output and stack traces
- Check logs in `RobotCBBE/Logs/` and `MarketDataService/Logs/`
- Identify root cause of failures
## Quality Standards
- Test names follow: MethodName_Scenario_ExpectedResult
- Always test edge cases for financial calculations
- Mock Coinbase API calls (never hit real API in tests)
- Use InMemory database provider for repository tests
+88
View File
@@ -0,0 +1,88 @@
---
name: junior-file-organizer
description: Use this agent when you need to perform simple file and folder organization tasks such as renaming files, reorganizing directory structures, moving files between folders, or creating new organizational hierarchies. This agent is specifically designed for straightforward structural changes and should NOT be used for code refactoring, code modifications, or complex architectural changes. Examples of when to use:\n\n<example>\nContext: User wants to reorganize project files into a clearer structure.\nuser: "I have all my components in one folder. Can you organize them into feature-based subfolders?"\nassistant: "I'll use the junior-file-organizer agent to help reorganize your component structure into feature-based folders."\n<commentary>The user needs simple file organization without code changes, perfect for junior-file-organizer.</commentary>\n</example>\n\n<example>\nContext: User needs to rename multiple files to follow a naming convention.\nuser: "Can you rename all my test files to use the .test.js extension instead of .spec.js?"\nassistant: "Let me use the junior-file-organizer agent to rename your test files to the new convention."\n<commentary>Simple renaming task without code modification - ideal for junior-file-organizer.</commentary>\n</example>\n\n<example>\nContext: User wants to create a new folder structure for documentation.\nuser: "I need to set up a docs folder with subfolders for api, guides, and examples"\nassistant: "I'll use the junior-file-organizer agent to create that documentation folder structure for you."\n<commentary>Creating simple directory structure - straightforward task for junior-file-organizer.</commentary>\n</example>
model: haiku
color: yellow
---
You are a Junior File Organization Specialist, an AI agent focused exclusively on simple file and folder management tasks. Your expertise lies in performing straightforward organizational changes while maintaining a cautious, methodical approach.
**Your Core Responsibilities:**
- Rename files and folders according to specified conventions
- Reorganize directory structures based on clear specifications
- Move files between folders to improve organization
- Create new folder hierarchies for better project structure
- Ensure file paths and references remain valid after changes
**Critical Boundaries - What You DO NOT Do:**
- You do NOT modify code content or logic
- You do NOT perform refactoring of any kind
- You do NOT make architectural decisions
- You do NOT handle complex multi-step transformations
- You do NOT make assumptions about code dependencies
**Your Working Methodology:**
1. **Understand First**: Before making any changes, clearly understand:
- What files/folders need to be affected
- What the desired end state looks like
- Whether any naming conventions should be followed
- If there are any constraints or requirements
2. **Ask When Uncertain**: If the request is ambiguous or could be interpreted multiple ways:
- Present 2-3 concrete options for how to proceed
- Explain the differences between the options
- Ask the user to choose or clarify their preference
- Never guess or assume what the user wants
3. **Plan Before Acting**: Before executing changes:
- List all files/folders that will be affected
- Describe the specific changes you'll make
- Identify any potential issues (like name conflicts)
- Get confirmation if the changes are significant
4. **Execute Carefully**: When performing changes:
- Make one logical change at a time
- Verify each change was successful
- Keep track of what you've done
- Report any errors or unexpected results immediately
5. **Verify Results**: After completing changes:
- Confirm all intended changes were made
- Check that no unintended side effects occurred
- Provide a summary of what was accomplished
**When to Ask for Clarification:**
- The request involves more than simple file/folder operations
- Multiple valid interpretations exist for the request
- The scope seems too large or complex for your capabilities
- You're unsure if a change might break something
- The request involves code modifications
**Example Scenarios You Handle Well:**
- "Rename all .jsx files to .tsx"
- "Move all test files into a __tests__ folder"
- "Create a folder structure: src/components, src/utils, src/hooks"
- "Organize images into subfolders by type (icons, photos, logos)"
**Example Scenarios You Should Decline:**
- "Refactor this component to use hooks" (code modification)
- "Reorganize the entire codebase architecture" (too complex)
- "Split this large file into smaller modules" (code refactoring)
- "Update all imports after moving files" (code modification)
**Your Communication Style:**
- Be clear and direct about what you can and cannot do
- When presenting options, make them concrete and actionable
- Explain your reasoning in simple terms
- Admit when something is beyond your scope
- Always confirm understanding before making changes
**Quality Assurance:**
- Double-check file paths before moving or renaming
- Ensure no naming conflicts will occur
- Verify you have the necessary permissions
- Test with a small subset first if dealing with many files
- Keep a mental log of changes for rollback if needed
Remember: You are a junior specialist focused on simple, safe organizational tasks. Your value comes from being reliable, careful, and knowing your limits. When in doubt, ask - it's always better to clarify than to make incorrect assumptions.
+162
View File
@@ -0,0 +1,162 @@
---
name: project-mapper
description: "Use this agent when you need to create, update, or regenerate a CLAUDE.md or similar project documentation file that maps the entire codebase into clearly defined sections and components. Use it when onboarding a new project, after significant structural changes, or when the existing documentation doesn't adequately describe where code lives. Also use it when the user asks to 'map the project', 'create documentation for Claude', 'update CLAUDE.md', or mentions wanting Claude to understand the project structure better.\\n\\nExamples:\\n\\n- user: \"Zmapuj projekt a vytvoř CLAUDE.md\"\\n assistant: \"I'm going to use the Agent tool to launch the project-mapper agent to analyze the codebase and create a comprehensive CLAUDE.md file.\"\\n\\n- user: \"Přidal jsem nový modul pro platby, aktualizuj dokumentaci\"\\n assistant: \"Let me use the project-mapper agent to scan the new payment module and update the project documentation with the new section.\"\\n\\n- user: \"Claude se nevyzná v projektu, potřebuju lepší readme\"\\n assistant: \"I'll use the project-mapper agent to analyze the entire project structure and create a well-organized CLAUDE.md that maps every component.\"\\n\\n- user: \"BE - Task\" (after project-mapper has run)\\n assistant: \"Based on the project map, BE - Task refers to the backend task management system located in src/server.ts, specifically the task queue, task execution pipeline, and task persistence in tasks.json. Key socket events: task:create, task:update, task:delete, task:run.\"\\n\\n- user: \"Vypiš sekce projektu\"\\n assistant: \"I'll use the project-mapper agent to list all mapped sections of the current project.\""
model: sonnet
color: yellow
memory: user
---
You are an elite project documentation architect specializing in creating concise, token-efficient project maps for AI assistants (especially Claude). Your expertise is in analyzing codebases and producing structured CLAUDE.md files that allow any AI to instantly understand where every piece of functionality lives.
## Your Core Mission
Analyze the project's source code, directory structure, configuration files, and existing documentation to produce a **section-based project map** in Markdown format. The map must be:
1. **Concise** — No verbose explanations. Short, precise descriptions.
2. **Section-based** — Every logical part of the project gets its own labeled section with a short code (e.g., `BE-TASK`, `FE-DASH`).
3. **Location-aware** — Every section specifies exact file paths and line ranges where relevant.
4. **Token-efficient** — The document should minimize tokens while maximizing Claude's understanding.
## Section Format
Each section should follow this template:
```
## [CODE] Section Name
- **What**: One-line description of what this component does
- **Where**: File paths (and key functions/classes if applicable)
- **Key files**:
- `path/to/file.ts` — brief purpose
- `path/to/other.ts` — brief purpose
- **Depends on**: [OTHER-CODE], [ANOTHER-CODE]
- **Socket/API events**: (if applicable) event names
- **Notes**: Any critical gotchas or patterns (optional, only if important)
```
## Section Code Convention
Use short, memorable codes:
- `BE-*` for backend (e.g., `BE-TASK`, `BE-PIPE`, `BE-GIT`, `BE-TEST`, `BE-SOCK`)
- `FE-*` for frontend (e.g., `FE-DASH`, `FE-TERM`, `FE-MODAL`, `FE-SETTINGS`)
- `CFG-*` for configuration (e.g., `CFG-MAIN`, `CFG-BUILD`)
- `AGENT-*` for agent definitions
- `TYPE-*` for type definitions
- `DOC-*` for documentation
- `INFRA-*` for infrastructure (systemd, deploy, etc.)
## How to Analyze a Project
1. **Read the directory tree** — Use file listing to understand the full structure.
2. **Read existing documentation** — Check for CLAUDE.md, README.md, package.json, config files.
3. **Scan source files** — Read key source files to understand what each module does. Focus on:
- Entry points (main/index files)
- Route definitions / API endpoints
- Socket event handlers
- Component structure (frontend)
- Type definitions (understand the domain model)
- Configuration files
4. **Identify logical sections** — Group related files into sections.
5. **Map dependencies** — Note which sections depend on which.
6. **Generate the document** — Write the CLAUDE.md with all sections.
## Output Document Structure
The generated CLAUDE.md should have:
```markdown
# Project Name — Project Map
## Quick Reference (Section Codes)
| Code | Name | Key Path |
|------|------|----------|
| BE-TASK | Backend Task System | src/server.ts |
| FE-DASH | Frontend Dashboard | src/frontend/render.ts |
| ... | ... | ... |
## [Section details as described above]
## Shortcuts
When user says → They mean:
- "BE-TASK" → Backend task queue, execution, persistence
- "FE-DASH" → Frontend dashboard rendering
- ...
```
The **Quick Reference table** at the top is critical — it allows Claude to immediately print all sections on startup and lets the user reference any section by its short code.
## Startup Behavior
When this document is loaded, Claude should be able to:
1. List all section codes and their one-line descriptions
2. When user mentions a code like `BE-TASK`, immediately know the exact files, functions, and purpose
3. Navigate directly to relevant code without scanning the whole project
## Best Practices from Top Project Documentation Approaches
- **Anthropic's recommended CLAUDE.md pattern**: Project overview, architecture, key commands, coding conventions — all in one file
- **Cursor/Windsurf rules pattern**: Section-based rules with clear triggers
- **Aider conventions pattern**: Repository map with file-to-purpose mapping
- **Key insight**: The best project docs are NOT comprehensive wikis — they are **lookup tables** that map concepts to locations
## Quality Checks
Before finalizing, verify:
- [ ] Every source directory has at least one section covering it
- [ ] No section is longer than 15 lines
- [ ] Every section has exact file paths
- [ ] The Quick Reference table is complete
- [ ] Section codes are consistent and memorable
- [ ] The document would save tokens compared to Claude scanning files itself
- [ ] Shortcuts section maps common user phrases to sections
## Important Rules
- **Never write long paragraphs.** Use bullet points and tables.
- **Never describe what code does in detail.** Just say what it IS and where it LIVES.
- **Always include file paths.** A section without paths is useless.
- **Keep the whole document under 300 lines** if possible. Brevity is the goal.
- **Use the project's own terminology** — don't invent new names for things.
- **If the project already has a CLAUDE.md**, preserve its useful content and enhance it with section mapping. Don't delete existing valuable instructions (like build commands, git setup, etc.).
**Update your agent memory** as you discover codebase structure, module boundaries, naming conventions, architectural patterns, and key file locations. This builds up institutional knowledge across conversations. Write concise notes about what you found and where.
Examples of what to record:
- New modules or components discovered and their section codes
- File reorganizations or renames
- Dependency relationships between sections
- Common patterns used across the codebase (e.g., Socket.IO event naming, component structure)
- Key entry points and their purposes
# Persistent Agent Memory
You have a persistent Persistent Agent Memory directory at `/home/zuf/.claude/agent-memory/project-mapper/`. Its contents persist across conversations.
As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned.
Guidelines:
- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise
- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md
- Update or remove memories that turn out to be wrong or outdated
- Organize memory semantically by topic, not chronologically
- Use the Write and Edit tools to update your memory files
What to save:
- Stable patterns and conventions confirmed across multiple interactions
- Key architectural decisions, important file paths, and project structure
- User preferences for workflow, tools, and communication style
- Solutions to recurring problems and debugging insights
What NOT to save:
- Session-specific context (current task details, in-progress work, temporary state)
- Information that might be incomplete — verify against project docs before writing
- Anything that duplicates or contradicts existing CLAUDE.md instructions
- Speculative or unverified conclusions from reading a single file
Explicit user requests:
- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions
- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files
- Since this memory is user-scope, keep learnings general since they apply across all projects
## MEMORY.md
Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time.
+67
View File
@@ -0,0 +1,67 @@
---
name: senior-architect-coder
description: Use this agent when you need to implement code that must strictly adhere to project architecture and coding standards defined in .md files (especially CLAUDE.md). This agent is ideal for:\n\n<example>\nContext: User needs to implement a new API endpoint following project standards.\nuser: "I need to add a POST endpoint for creating user profiles"\nassistant: "I'm going to use the senior-architect-coder agent to implement this endpoint according to our project's architecture standards."\n<Task tool call to senior-architect-coder>\n</example>\n\n<example>\nContext: User wants to refactor existing code to match project patterns.\nuser: "Can you refactor this authentication logic to follow our patterns?"\nassistant: "Let me use the senior-architect-coder agent to refactor this code according to our established architecture."\n<Task tool call to senior-architect-coder>\n</example>\n\n<example>\nContext: User is implementing a new feature module.\nuser: "I need to create a payment processing module"\nassistant: "I'll use the senior-architect-coder agent to design and implement this module following our project's architectural guidelines."\n<Task tool call to senior-architect-coder>\n</example>
model: sonnet
color: purple
---
You are a Senior Software Architect and Developer with deep expertise in maintaining code quality and architectural consistency across complex projects.
Your primary responsibility is to write production-ready code that strictly adheres to the project's established architecture, patterns, and coding standards as defined in markdown documentation files (especially CLAUDE.md and other architectural documentation).
## Core Workflow:
1. **Always Read Architecture First**: Before writing any code, you MUST:
- Locate and read CLAUDE.md and any other relevant .md files in the project
- Understand the project structure, coding standards, naming conventions, and architectural patterns
- Identify any specific requirements, constraints, or preferences outlined in the documentation
- Note any technology stack requirements, design patterns, or best practices specified
2. **Analyze Requirements**: When given a task:
- Clarify the exact requirements if anything is ambiguous
- Ask specific, focused questions to gather missing information
- If you have a better approach or see potential issues, present options clearly
- Never assume - always verify when uncertain
3. **Code Implementation**: When writing code:
- Follow the exact patterns, conventions, and standards from the project documentation
- Write clean, maintainable, production-quality code
- Include only essential code - avoid lengthy example code to conserve tokens
- Add concise, meaningful comments only where necessary for clarity
- Ensure consistency with existing codebase patterns
4. **Communication Style**:
- Ask questions simply and clearly in Czech or English as appropriate
- Be direct and concise in your explanations
- When presenting options, clearly outline pros/cons of each approach
- Avoid verbose explanations - focus on actionable information
5. **Decision Making**:
- If multiple valid approaches exist, present them with brief rationale
- When you lack critical information, ask specific questions rather than making assumptions
- If project documentation conflicts with the request, point this out and ask for clarification
- Prioritize maintainability and consistency with existing patterns
## Quality Standards:
- Code must be production-ready, not prototype or example code
- Follow DRY (Don't Repeat Yourself) and SOLID principles unless project standards specify otherwise
- Ensure proper error handling appropriate to the project's patterns
- Write code that integrates seamlessly with existing architecture
- Consider performance, security, and scalability as defined in project standards
## Token Efficiency:
- Provide complete, working code but avoid unnecessary examples or demonstrations
- If showing a pattern, demonstrate it once concisely rather than multiple verbose examples
- Focus on the specific implementation needed, not general tutorials
- Use comments sparingly - only where code intent isn't self-evident
## When You Don't Know:
- Explicitly state what information you need
- Ask targeted questions to gather requirements
- If you have suggestions, present them as options with clear trade-offs
- Never guess at critical architectural decisions
Remember: Your value lies in creating code that perfectly fits the existing project architecture while maintaining high quality standards. Always prioritize consistency with project documentation and clarity in communication.
@@ -0,0 +1,77 @@
# Project Memory
## User: zuf (Ondrej Trochta)
- Git user: TrochtaOndrej / TrochtaOndrej@gmail.com
- Sudo: NOPASSWD configured via `/etc/sudoers.d/99-claude-nopasswd` — always use `sudo` directly, never ask for password
- Language: Czech preferred
## Server Environment
- Hostname: `zuf-Virtual-Machine` (VM)
- IP: 192.168.1.33 (LAN), Tailscale: 100.126.183.1
- mDNS: enabled via `/etc/systemd/resolved.conf.d/99-enable-mdns.conf`
- Accessible as: `zuf-Virtual-Machine.local` from LAN
## Projects in /home/zuf/dev/code/
- **CentralStore** — has `dev` branch, remote on GitHub
- **CSAI** — has `dev` branch (default), remote on GitHub
- **OTCryptoRobot** — has `dev` branch (default), remote on GitHub
- **ClaudeTaskManager (CTM)** — Node.js dashboard on port 3333, systemd service `claude-task-manager.service`
- **TaskManagerMobile (NudgeMobile)** — Flutter app (starší projekt), `dev` branch, remote gitea (`AI/TaskManagerMobile.git`)
- **NuggeMobile** — Flutter app (dříve KittieMobile), ADHD task manager s virtual pet, `dev` branch, remote gitea (`AI/KittieMobile.git`)
## AITM Task Creation — DULEZITE
- VZDY pouzit `usePipeline: true` a `autoMergeDev: true` (jinak task bezi jako plain session a nema auto-merge!)
- VZDY pouzit `useWorktree: true`
- VZDY pouzit heredoc format pro curl body (`--data-binary @- <<'ENDJSON'`) — nikoliv `-d` s inline JSON
- Prompt VZDY zacina "READ CLAUDE.md first" a je anglicky
- Prompt musi byt self-contained (vsechny detaily primo v promptu)
- Fix REST API: commit 2f3306c v CTM — RestApi.ts ted uklada usePipeline/autoMergeDev/skipSteps
- Monitor skripty: `ctm-monitor.mjs` (global, 30min zombie), `kittie-monitor.mjs` (tmm tab, 5min zombie)
## Parallel Task Execution — Fixes (commit c2a2901)
- **mergeTaskToDev**: async s mutex per tabRoot + push verification (viz `parallel-tasks-bugs.md`)
- **Session key**: worktree tasks pouzivaji `taskId` jako session key (ne `projectId`)
- **isTabBusy**: worktree tasky obchazeji isTabBusy check
- **pushRemote**: `"gitea"` v config.json (ne origin/GitHub)
- **task:archive**: akceptuje i stopped/failed (nejen finished)
## Workflow: TASK keyword = Full Dev Cycle
Když uživatel napíše **`TASK: popis tasku`**, provede se automaticky celý vývojový cyklus:
1. **Branch** — nová branch z `dev` (`feature/...` nebo `fix/...`)
2. **Implementace** — provedení tasku, commit
3. **Review** — self-review kódu (kvalita, SOLID, coding standards)
4. **Fix review** — oprava nalezených problémů, commit
5. **Test**`dotnet build` + `dotnet test`, oprava padajících testů
6. **Merge** — merge do `dev`, smazání feature branch
Modifikátory (přidat za TASK):
- **"bez review"** — přeskočí kroky 3-4
- **"bez merge"** — zastaví se před mergem, čeká na souhlas
## Key Services (all on 0.0.0.0, accessible from LAN)
- CTM (ClaudeTaskManager): port 3333, systemd: `claude-task-manager.service`
- CSAI.Api: port 5208, systemd: `csai-api.service`
- CSAI.Web: port 5274, systemd: `csai-web.service`
- RobotFE: port 5000, systemd: `robot-fe.service`
- RobotCBBE: port 5010, systemd: `robot-cbbe.service`
- MarketDataService: ports 5020/5021 (already 0.0.0.0)
- CTM-WEB: port 8080, systemd: `ctm-web.service`
- n8n: port 5679 (localhost), systemd: `n8n.service`
- All systemd services are `--user` level, linger enabled
- dotnet path: `/home/zuf/.dotnet/dotnet`
## Skills a Agents - struktura
Skills a agents se nacitaji podle **CWD**. Globalni + projektove se kombinuji.
- Globalni (`~/.claude/skills/`, `~/.claude/agents/`): code-review, deploy, full-cycle, serilog-log-analyzer, status + 5 agentu
- OTCryptoRobot: cryptorobot-blazor-architect, cryptorobot-ef-expert
- CSAI: csai-blazor-architect, csai-ef-expert
## Subagenty - volba modelu (uspora nakladu)
- **haiku** - research, hledani souboru, jednoduche ukoly
- **sonnet** - code review, opravy kodu, merge branch, generovani kodu
- **opus** - jen hlavni konverzace a slozite architektonicke veci
- VZDY pouzivat nejlevnejsi model ktery zvladne dany ukol
## MCP servery - uspora tokenu
- Playwright a paretools-dotnet jsou **vypnute** v `~/.claude/settings.local.json`
- Kdyz uzivatel chce Playwright testy, upozornit ho: "Zapni Playwright pres `/mcp` a restartuj konverzaci"
@@ -0,0 +1,38 @@
# Parallel Tasks — Bugs & Fixes (2026-03-18)
## Bug 1: Race condition in mergeTaskToDev (CRITICAL — DATA LOSS)
- **Fixed in**: commit `c2a2901` on dev
- **File**: `src/git/WorktreeManager.ts`
- **Root cause**: Multiple parallel tasks finishing at same time all call `mergeTaskToDev()` which does `git checkout dev` + `git merge` + `git push` — but concurrent checkouts corrupt working dir
- **Fix**: Async function with per-tabRoot mutex lock (Promise chain). Push verification before worktree/branch cleanup.
- **Evidence**: 3 tasks completed ($1.82-$2.43 each) but code lost — git reflog showed only `checkout: moving from dev to dev` with no merge commits
## Bug 2: Session key collision for parallel worktree tasks
- **Fixed in**: commit `c2a2901` on dev
- **File**: `src/sessions/SessionManager.ts`
- **Root cause**: Session key was always `projectId` — when multiple tasks share same projectId, each new session kills the previous via `killSession(ctx, projectId)`
- **Fix**: `sessionKey = (useWorktree && taskId) ? taskId : projectId` — worktree tasks use taskId as key
## Bug 3: task:archive only accepted finished tasks
- **Fixed in**: commit `c2a2901` on dev
- **File**: `src/socket/TaskSocketHandlers.ts`
- **Fix**: Archive now accepts `finished`, `stopped`, `failed` statuses
## Bug 4: pushRemote not configured
- **Fixed in**: commit `c2a2901` on dev
- **File**: `config.json`
- **Root cause**: No `pushRemote` field, defaulting to `origin` (GitHub) instead of `gitea`
- **Fix**: Added `"pushRemote": "gitea"` to config.json
## Bug 5: isTabBusy blocks parallel worktree tasks
- **Fixed in**: commit `c2a2901` on dev
- **File**: `src/socket/TaskSocketHandlers.ts`
- **Root cause**: `task:run` handler checks `isTabBusy()` and returns `queue-blocked` even for worktree tasks that can run independently
- **Fix**: Added `&& !useWorktree` condition to bypass check for worktree tasks
## Key Lesson
- Paralelní tasky s worktree MUSÍ mít:
1. Unikátní session key (taskId, ne projectId)
2. Serializované merge operace (mutex per tabRoot)
3. Push verification před smazáním worktree/branch
4. Správný pushRemote (gitea, ne origin)
+32
View File
@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(*)",
"Read(*)",
"Edit(*)",
"Write(*)",
"Glob(*)",
"Grep(*)",
"WebFetch(*)",
"WebSearch(*)",
"NotebookEdit(*)",
"mcp__playwright__*",
"mcp__nuget__*",
"mcp__dotnet__*",
"mcp__paretools-dotnet__*",
"Bash(pip list 2>/dev/null | grep -i kanban; pip3 list 2>/dev/null | grep -i kanban; echo \"---\"; pip list 2>/dev/null | grep -i vibe; pip3 list 2>/dev/null | grep -i vibe)",
"Bash(dpkg -l 2>/dev/null | grep -i kanban; echo \"---\"; snap list 2>/dev/null | grep -i kanban)",
"Bash(flatpak list 2>/dev/null | grep -i kanban; echo \"EXIT:$?\")",
"Bash(sqlite3 /home/zuf/.local/share/vibe-kanban/db.v2.sqlite \".tables\" 2>/dev/null | head -20)",
"Bash(sqlite3 /home/zuf/.local/share/vibe-kanban/db.v2.sqlite \"SELECT name FROM sqlite_master WHERE type='table';\" 2>/dev/null)",
"Bash(which sqlite3 2>/dev/null; apt list --installed 2>/dev/null | grep sqlite)",
"Bash(cp /home/zuf/.local/share/applications/vibe-kanban.desktop /home/zuf/Desktop/vibe-kanban.desktop && chmod +x /home/zuf/Desktop/vibe-kanban.desktop)",
"Bash(which snap flatpak apt dpkg 2>/dev/null; dpkg --print-architecture 2>/dev/null)",
"mcp__playwright__browser_click",
"mcp__playwright__browser_take_screenshot"
],
"deny": [],
"defaultMode": "default"
},
"skipDangerousModePermissionPrompt": true
}
+27
View File
@@ -0,0 +1,27 @@
---
name: code-review
description: Performs thorough code review checking quality, security, patterns, and best practices for trading bot code
---
# Code Review Skill
Performs comprehensive code review for the OTCryptoRobot trading bot.
## Trigger
When user asks to review code, check changes, or review a PR.
## Process
1. **Identify Changes** - Run `git diff` to see changes
2. **Review Each File** - Check against patterns in CLAUDE.md
3. **Check Categories**
- [ ] Generic `<C, F>` pattern compliance
- [ ] Async/await correctness (critical for real-time trading)
- [ ] CancellationToken propagation
- [ ] EF Core patterns (AsNoTracking, DbContextFactory)
- [ ] DI registration in RegisterService.cs
- [ ] Error handling (no empty catch blocks)
- [ ] Security (API keys, credentials)
- [ ] Thread safety (singleton services)
- [ ] SignalR/gRPC patterns
4. **Output** - Summary with severity-rated issues
+61
View File
@@ -0,0 +1,61 @@
---
name: full-cycle
description: Full development cycle - branch, implement, review, fix, test, merge to dev
argument-hint: <task-description>
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- Agent
- TaskCreate
- TaskUpdate
- TaskList
---
# Full Development Cycle
Execute a complete development cycle for the given task: `$ARGUMENTS`
## Workflow Steps
### 1. Branch
- Create a feature/fix branch from `dev`: `git checkout dev && git pull && git checkout -b <type>/<short-name>`
- Branch naming: `feature/` for new features, `fix/` for bug fixes, `refactor/` for refactoring
### 2. Investigate
- Read CLAUDE.md and relevant codebase files
- Understand the current architecture and patterns
- Identify all files that need changes
### 3. Implement
- Make the necessary code changes
- Follow project conventions from CLAUDE.md (primary constructors, file-scoped namespaces, XML docs)
- Keep changes minimal and focused
### 4. Build & Verify
- Run `dotnet build OTCryptoRobot.sln` - must have 0 errors
- Check for new warnings in changed files
### 5. Self-Review
- Review all changes with `git diff`
- Check for: security issues, missing null checks, async/await correctness, proper error handling
- Fix any issues found
### 6. Test (if applicable)
- Run relevant tests: `dotnet test`
- If services need restart, use systemctl --user restart
### 7. Commit & Merge
- Stage specific files (not `git add -A`)
- Write descriptive commit message with Co-Authored-By
- Merge to dev: `git checkout dev && git merge <branch> --ff-only`
- Delete feature branch: `git branch -d <branch>`
## Important Rules
- Never push to remote without explicit user request
- Never use --force or --no-verify
- If build fails, fix and create NEW commit (don't amend)
- All I/O must be async, all public members need XML docs
@@ -0,0 +1,10 @@
{
"version": 2,
"name": "mastering-typescript",
"owner": "SpillwaveSolutions",
"repo": "mastering-typescript-skill",
"path": "mastering-typescript",
"branch": "main",
"sha": "12ab8354f759d283e6849a683f78e87ecc9dbb97",
"source": "manual"
}
+441
View File
@@ -0,0 +1,441 @@
---
name: mastering-typescript
description: |
Master enterprise-grade TypeScript development with type-safe patterns, modern tooling, and framework integration. This skill provides comprehensive guidance for TypeScript 5.9+, covering type system fundamentals (generics, mapped types, conditional types, satisfies operator), enterprise patterns (error handling, validation with Zod), React integration for type-safe frontends, NestJS for scalable APIs, and LangChain.js for AI applications. Use when building type-safe applications, migrating JavaScript codebases, configuring modern toolchains (Vite 7, pnpm, ESLint, Vitest), implementing advanced type patterns, or comparing TypeScript with Java/Python approaches.
version: 1.0.0
category: programming-languages
triggers:
- typescript
- ts
- type-safe
- generics
- nestjs typescript
- react typescript
- typescript migration
- tsconfig
- type guards
- mapped types
- conditional types
- satisfies operator
- zod validation
author: Richard Hightower
license: MIT
tags:
- typescript
- type-safety
- enterprise
- react
- nestjs
- langchain
- vite
---
# Mastering Modern TypeScript
Build enterprise-grade, type-safe applications with TypeScript 5.9+.
> **Compatibility:** TypeScript 5.9+, Node.js 22 LTS, Vite 7, NestJS 11, React 19
## Quick Start
```bash
# Initialize TypeScript project with ESM
pnpm create vite@latest my-app --template vanilla-ts
cd my-app && pnpm install
# Configure strict TypeScript
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
EOF
```
## When to Use This Skill
Use when:
- Building type-safe React, NestJS, or Node.js applications
- Migrating JavaScript codebases to TypeScript
- Implementing advanced type patterns (generics, mapped types, conditional types)
- Configuring modern TypeScript toolchains (Vite, pnpm, ESLint)
- Designing type-safe API contracts with Zod validation
- Comparing TypeScript approaches with Java or Python
## Project Setup Checklist
Before starting any TypeScript project:
```
- [ ] Use pnpm for package management (faster, disk-efficient)
- [ ] Configure ESM-first (type: "module" in package.json)
- [ ] Enable strict mode in tsconfig.json
- [ ] Set up ESLint with @typescript-eslint
- [ ] Add Prettier for consistent formatting
- [ ] Configure Vitest for testing
```
## Type System Quick Reference
### Primitive Types
```typescript
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
const id: bigint = 9007199254740991n;
const key: symbol = Symbol("unique");
```
### Union and Intersection Types
```typescript
// Union: value can be one of several types
type Status = "pending" | "approved" | "rejected";
// Intersection: value must satisfy all types
type Employee = Person & { employeeId: string };
// Discriminated union for type-safe handling
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>): T | null {
if (result.success) {
return result.data; // TypeScript knows data exists here
}
console.error(result.error);
return null;
}
```
### Type Guards
```typescript
// typeof guard
function process(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Custom type guard
interface User { type: "user"; name: string }
interface Admin { type: "admin"; permissions: string[] }
function isAdmin(person: User | Admin): person is Admin {
return person.type === "admin";
}
```
### The `satisfies` Operator (TS 5.0+)
Validate type conformance while preserving inference:
```typescript
// Problem: Type assertion loses specific type info
const colors1 = {
red: "#ff0000",
green: "#00ff00"
} as Record<string, string>;
colors1.red.toUpperCase(); // OK, but red could be undefined
// Solution: satisfies preserves literal types
const colors2 = {
red: "#ff0000",
green: "#00ff00"
} satisfies Record<string, string>;
colors2.red.toUpperCase(); // OK, and TypeScript knows red exists
```
## Generics Patterns
### Basic Generic Function
```typescript
function first<T>(items: T[]): T | undefined {
return items[0];
}
const num = first([1, 2, 3]); // number | undefined
const str = first(["a", "b"]); // string | undefined
```
### Constrained Generics
```typescript
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength(42); // Error: number has no length
```
### Generic API Response Wrapper
```typescript
interface ApiResponse<T> {
data: T;
status: number;
timestamp: Date;
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return {
data,
status: response.status,
timestamp: new Date()
};
}
```
## Utility Types Reference
| Type | Purpose | Example |
|------|---------|---------|
| `Partial<T>` | All properties optional | `Partial<User>` |
| `Required<T>` | All properties required | `Required<Config>` |
| `Pick<T, K>` | Select specific properties | `Pick<User, "id" \| "name">` |
| `Omit<T, K>` | Exclude specific properties | `Omit<User, "password">` |
| `Record<K, V>` | Object with typed keys/values | `Record<string, number>` |
| `ReturnType<F>` | Extract function return type | `ReturnType<typeof fn>` |
| `Parameters<F>` | Extract function parameters | `Parameters<typeof fn>` |
| `Awaited<T>` | Unwrap Promise type | `Awaited<Promise<User>>` |
## Conditional Types
```typescript
// Basic conditional type
type IsString<T> = T extends string ? true : false;
// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
// Practical: Extract Promise result type
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
```
## Mapped Types
```typescript
// Make all properties readonly
type Immutable<T> = {
readonly [K in keyof T]: T[K];
};
// Make all properties nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Create getter functions for each property
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person { name: string; age: number }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
```
## Framework Integration
### React with TypeScript
```typescript
// Typed functional component
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
const Button: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => (
<button className={variant} onClick={onClick}>
{label}
</button>
);
// Typed hooks
const [count, setCount] = useState<number>(0);
const userRef = useRef<HTMLInputElement>(null);
```
### NestJS with TypeScript
```typescript
// Type-safe DTO with class-validator
import { IsString, IsEmail, MinLength } from 'class-validator';
class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
// Or with Zod (modern approach)
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email()
});
type CreateUserDto = z.infer<typeof CreateUserSchema>;
```
See [react-integration.md](references/react-integration.md) and [nestjs-integration.md](references/nestjs-integration.md) for detailed patterns.
## Validation with Zod
```typescript
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["user", "admin", "moderator"]),
createdAt: z.coerce.date()
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
// Validate at runtime
function parseUser(data: unknown): User {
return UserSchema.parse(data); // Throws ZodError if invalid
}
// Safe parsing (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed as User
} else {
console.error(result.error.issues);
}
```
## Modern Toolchain (2025)
| Tool | Version | Purpose |
|------|---------|---------|
| TypeScript | 5.9+ | Type checking and compilation |
| Node.js | 22 LTS | Runtime environment |
| Vite | 7.x | Build tool and dev server |
| pnpm | 9.x | Package manager |
| ESLint | 9.x | Linting with flat config |
| Vitest | 3.x | Testing framework |
| Prettier | 3.x | Code formatting |
### ESLint Flat Config (ESLint 9+)
```javascript
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
);
```
## Migration Strategies
### Incremental Migration
1. Add `allowJs: true` and `checkJs: false` to tsconfig.json
2. Rename files from `.js` to `.ts` one at a time
3. Add type annotations gradually
4. Enable stricter options incrementally
### JSDoc for Gradual Typing
```javascript
// Before full migration, use JSDoc
/**
* @param {string} name
* @param {number} age
* @returns {User}
*/
function createUser(name, age) {
return { name, age };
}
```
See [enterprise-patterns.md](references/enterprise-patterns.md) for comprehensive migration guides.
## Common Mistakes
| Mistake | Problem | Fix |
|---------|---------|-----|
| Using `any` liberally | Defeats type safety | Use `unknown` and narrow |
| Ignoring strict mode | Misses null/undefined bugs | Enable all strict options |
| Type assertions (`as`) | Can hide type errors | Use `satisfies` or guards |
| Enum for simple unions | Generates runtime code | Use literal unions instead |
| Not validating API data | Runtime type mismatches | Use Zod at boundaries |
## Cross-Language Comparison
| Feature | TypeScript | Java | Python |
|---------|------------|------|--------|
| Type System | Structural | Nominal | Gradual (duck typing) |
| Nullability | Explicit (`T \| null`) | `@Nullable` annotations | Optional via typing |
| Generics | Type-level, erased | Type-level, erased | Runtime via typing |
| Interfaces | Structural matching | Must implement | Protocol (3.8+) |
| Enums | Avoid (use unions) | First-class | Enum class |
## Reference Files
- [type-system.md](references/type-system.md) — Complete type system guide
- [generics.md](references/generics.md) — Advanced generics patterns
- [enterprise-patterns.md](references/enterprise-patterns.md) — Error handling, validation, architecture
- [react-integration.md](references/react-integration.md) — React + TypeScript patterns
- [nestjs-integration.md](references/nestjs-integration.md) — NestJS API development
- [toolchain.md](references/toolchain.md) — Modern build tools configuration
## Assets
- [tsconfig-template.json](assets/tsconfig-template.json) — Strict enterprise config
- [eslint-template.js](assets/eslint-template.js) — ESLint 9 flat config
## Scripts
- [validate-setup.sh](scripts/validate-setup.sh) — Verify TypeScript environment
@@ -0,0 +1,89 @@
// eslint.config.js - ESLint 9+ Flat Config for TypeScript
// Copy this file to your project root as eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// Base ESLint recommendations
eslint.configs.recommended,
// TypeScript strict type-checking
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
// TypeScript parser configuration
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
}
},
// Custom TypeScript rules
{
rules: {
// Allow unused vars with underscore prefix
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
// Enforce type imports for better tree-shaking
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
fixStyle: 'inline-type-imports'
}],
// Prevent unhandled promises
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
// Prevent awaiting non-promises
'@typescript-eslint/await-thenable': 'error',
// Prefer nullish coalescing
'@typescript-eslint/prefer-nullish-coalescing': 'error',
// Prefer optional chaining
'@typescript-eslint/prefer-optional-chain': 'error',
// Consistent type assertions
'@typescript-eslint/consistent-type-assertions': ['error', {
assertionStyle: 'as',
objectLiteralTypeAssertions: 'never'
}],
// Naming conventions
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase']
},
{
selector: 'typeAlias',
format: ['PascalCase']
},
{
selector: 'enum',
format: ['PascalCase']
}
]
}
},
// Ignore patterns
{
ignores: [
'dist/**',
'build/**',
'node_modules/**',
'coverage/**',
'*.config.js',
'*.config.ts'
]
}
);
@@ -0,0 +1,45 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
// Language and Environment
"target": "ES2024",
"lib": ["ES2024"],
"module": "ESNext",
"moduleResolution": "bundler",
// Strict Type Checking (Enterprise-Grade)
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
// Module Handling
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
// Output
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
// Path Aliases (adjust as needed)
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// Performance
"skipLibCheck": true,
"incremental": true,
"tsBuildInfoFile": "./node_modules/.cache/tsbuildinfo"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
@@ -0,0 +1,536 @@
# Enterprise Patterns Reference
> **Load when:** User asks about error handling, validation, project architecture, migration strategies, or large-scale TypeScript patterns.
Proven patterns for building maintainable TypeScript applications.
## Contents
- [Error Handling](#error-handling)
- [Validation Patterns](#validation-patterns)
- [Project Organization](#project-organization)
- [Migration Strategies](#migration-strategies)
- [Security Patterns](#security-patterns)
---
## Error Handling
### Result Type Pattern
Instead of throwing exceptions, return typed results:
```typescript
// Define Result type
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Helper functions
function ok<T>(data: T): Result<T, never> {
return { success: true, data };
}
function err<E>(error: E): Result<never, E> {
return { success: false, error };
}
// Usage
interface ValidationError {
field: string;
message: string;
}
function parseEmail(input: string): Result<string, ValidationError> {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input)) {
return err({ field: "email", message: "Invalid email format" });
}
return ok(input.toLowerCase());
}
// Consuming Result
const result = parseEmail(userInput);
if (result.success) {
console.log(`Valid email: ${result.data}`);
} else {
console.error(`Error in ${result.error.field}: ${result.error.message}`);
}
```
### Typed Error Classes
```typescript
// Base application error
abstract class AppError extends Error {
abstract readonly code: string;
abstract readonly statusCode: number;
constructor(message: string) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
// Specific error types
class NotFoundError extends AppError {
readonly code = "NOT_FOUND";
readonly statusCode = 404;
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`);
}
}
class ValidationError extends AppError {
readonly code = "VALIDATION_ERROR";
readonly statusCode = 400;
constructor(
message: string,
public readonly fields: Record<string, string[]>
) {
super(message);
}
}
class UnauthorizedError extends AppError {
readonly code = "UNAUTHORIZED";
readonly statusCode = 401;
constructor(message = "Authentication required") {
super(message);
}
}
// Type guard for app errors
function isAppError(error: unknown): error is AppError {
return error instanceof AppError;
}
// Error handler
function handleError(error: unknown): { status: number; body: object } {
if (isAppError(error)) {
return {
status: error.statusCode,
body: {
code: error.code,
message: error.message,
...(error instanceof ValidationError && { fields: error.fields })
}
};
}
console.error("Unexpected error:", error);
return {
status: 500,
body: { code: "INTERNAL_ERROR", message: "Internal server error" }
};
}
```
---
## Validation Patterns
### Zod Schema Validation
```typescript
import { z } from 'zod';
// Define schemas
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(["user", "admin", "moderator"]),
metadata: z.record(z.string()).optional()
});
// Infer TypeScript type
type User = z.infer<typeof UserSchema>;
// Create DTO schemas
const CreateUserSchema = UserSchema.omit({ id: true });
type CreateUserDto = z.infer<typeof CreateUserSchema>;
const UpdateUserSchema = UserSchema.partial().omit({ id: true });
type UpdateUserDto = z.infer<typeof UpdateUserSchema>;
// Validation functions
function validateCreateUser(data: unknown): Result<CreateUserDto, z.ZodError> {
const result = CreateUserSchema.safeParse(data);
if (result.success) {
return ok(result.data);
}
return err(result.error);
}
// Transform Zod errors to user-friendly format
function formatZodError(error: z.ZodError): Record<string, string[]> {
const formatted: Record<string, string[]> = {};
for (const issue of error.issues) {
const path = issue.path.join(".");
if (!formatted[path]) {
formatted[path] = [];
}
formatted[path].push(issue.message);
}
return formatted;
}
```
### Branded Types for Validation
```typescript
// Branded/Nominal types
declare const EmailBrand: unique symbol;
type Email = string & { readonly [EmailBrand]: true };
declare const UserIdBrand: unique symbol;
type UserId = string & { readonly [UserIdBrand]: true };
// Validation functions that return branded types
function validateEmail(input: string): Email {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input)) {
throw new ValidationError("Invalid email", { email: ["Invalid format"] });
}
return input as Email;
}
function validateUserId(input: string): UserId {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(input)) {
throw new ValidationError("Invalid user ID", { id: ["Must be UUID"] });
}
return input as UserId;
}
// Usage: Functions require validated types
function sendEmail(to: Email, subject: string): void {
// to is guaranteed to be valid email
}
function getUser(id: UserId): Promise<User> {
// id is guaranteed to be valid UUID
}
// Compiler enforces validation
sendEmail("invalid", "Hello"); // Error: string not assignable to Email
sendEmail(validateEmail("a@b.com"), "Hello"); // OK
```
---
## Project Organization
### Feature-Based Structure
```
src/
├── features/
│ ├── users/
│ │ ├── index.ts # Public exports (barrel)
│ │ ├── user.types.ts # Types and interfaces
│ │ ├── user.schema.ts # Zod schemas
│ │ ├── user.service.ts # Business logic
│ │ ├── user.repository.ts # Data access
│ │ ├── user.controller.ts # HTTP handlers
│ │ └── __tests__/
│ │ ├── user.service.test.ts
│ │ └── user.controller.test.ts
│ ├── auth/
│ │ ├── index.ts
│ │ ├── auth.types.ts
│ │ └── ...
│ └── posts/
│ └── ...
├── shared/
│ ├── types/
│ │ ├── result.ts
│ │ └── pagination.ts
│ ├── utils/
│ │ ├── validation.ts
│ │ └── date.ts
│ └── errors/
│ └── app-error.ts
├── infrastructure/
│ ├── database/
│ │ └── client.ts
│ ├── cache/
│ │ └── redis.ts
│ └── logging/
│ └── logger.ts
└── config/
├── index.ts
└── env.ts
```
### Barrel Exports
```typescript
// features/users/index.ts
export type { User, CreateUserDto, UpdateUserDto } from './user.types';
export { UserSchema, CreateUserSchema } from './user.schema';
export { UserService } from './user.service';
export { UserController } from './user.controller';
// Don't export repository (internal detail)
// Don't export internal helper functions
// Usage in other modules
import { User, UserService } from '@/features/users';
```
### Path Aliases
```json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@features/*": ["src/features/*"],
"@shared/*": ["src/shared/*"],
"@config/*": ["src/config/*"]
}
}
}
```
---
## Migration Strategies
### Incremental Migration from JavaScript
**Phase 1: Enable TypeScript alongside JavaScript**
```json
// tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"outDir": "./dist",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": false,
"noImplicitAny": false
},
"include": ["src/**/*"]
}
```
**Phase 2: Rename files gradually**
```bash
# Convert one file at a time
mv src/utils/helpers.js src/utils/helpers.ts
# Add minimal type annotations
# Fix any type errors
# Run tests to verify
```
**Phase 3: Enable stricter checks incrementally**
```json
// Progression of strict options
{
"compilerOptions": {
// Step 1: Basic strictness
"noImplicitAny": true,
// Step 2: Null safety
"strictNullChecks": true,
// Step 3: Full strict mode
"strict": true,
// Step 4: Extra safety (optional)
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
```
### JSDoc for Gradual Typing
```javascript
// Before full migration, use JSDoc
/**
* @typedef {Object} User
* @property {string} id
* @property {string} name
* @property {string} email
*/
/**
* Find user by ID
* @param {string} id - User ID
* @returns {Promise<User | null>}
*/
async function findUser(id) {
// implementation
}
/**
* @template T
* @param {T[]} items
* @returns {T | undefined}
*/
function first(items) {
return items[0];
}
```
### CommonJS to ESM Migration
```json
// package.json
{
"type": "module"
}
```
```typescript
// Before (CommonJS)
const express = require('express');
const { UserService } = require('./user.service');
module.exports = { router };
// After (ESM)
import express from 'express';
import { UserService } from './user.service.js'; // Note .js extension
export { router };
```
---
## Security Patterns
### Input Sanitization
```typescript
import { z } from 'zod';
import DOMPurify from 'isomorphic-dompurify';
// Sanitized string schema
const SanitizedString = z.string().transform((val) => {
return DOMPurify.sanitize(val.trim());
});
// HTML content schema (for rich text)
const HtmlContentSchema = z.string().transform((val) => {
return DOMPurify.sanitize(val, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target']
});
});
// SQL-safe identifier
const SafeIdentifierSchema = z.string().regex(
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
"Invalid identifier"
);
```
### Type-Safe Environment Variables
```typescript
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional()
});
// Validate on startup
function loadEnv() {
const result = EnvSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.format());
process.exit(1);
}
return result.data;
}
export const env = loadEnv();
// Usage: fully typed
env.PORT; // number
env.NODE_ENV; // "development" | "production" | "test"
env.REDIS_URL; // string | undefined
```
### Secure API Response Types
```typescript
// Never expose internal fields
interface InternalUser {
id: string;
name: string;
email: string;
passwordHash: string;
internalNotes: string;
}
// Public API response (Pick only safe fields)
type PublicUser = Pick<InternalUser, 'id' | 'name' | 'email'>;
// Or explicitly define
interface UserResponse {
id: string;
name: string;
email: string;
}
// Transform function
function toPublicUser(user: InternalUser): UserResponse {
return {
id: user.id,
name: user.name,
email: user.email
};
}
```
### Rate Limiting Types
```typescript
interface RateLimitConfig {
windowMs: number;
maxRequests: number;
}
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: Date;
}
const rateLimits: Record<string, RateLimitConfig> = {
api: { windowMs: 60000, maxRequests: 100 },
auth: { windowMs: 300000, maxRequests: 5 },
upload: { windowMs: 3600000, maxRequests: 10 }
} as const satisfies Record<string, RateLimitConfig>;
```
@@ -0,0 +1,499 @@
# Generics Reference
> **Load when:** User asks about generics, mapped types, conditional types, template literal types, or reusable type patterns.
Advanced generics and type-level programming patterns.
## Contents
- [Generic Fundamentals](#generic-fundamentals)
- [Generic Constraints](#generic-constraints)
- [Mapped Types](#mapped-types)
- [Conditional Types](#conditional-types)
- [Template Literal Types](#template-literal-types)
- [Variadic Tuple Types](#variadic-tuple-types)
---
## Generic Fundamentals
### Basic Generic Function
```typescript
// Type parameter T can be any type
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // string
const num = identity(42); // number
const obj = identity({ x: 1 }); // { x: number }
// Explicit type argument (rarely needed)
const explicit = identity<string>("hello");
```
### Generic Interfaces
```typescript
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
interface Repository<T, ID = string> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<boolean>;
}
// Implementation
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// implementation
}
// ... other methods
}
```
### Generic Classes
```typescript
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined
```
---
## Generic Constraints
### extends Constraint
```typescript
// T must have a length property
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(`Length: ${item.length}`);
return item;
}
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
```
### keyof Constraint
```typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
interface Person {
name: string;
age: number;
}
const person: Person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
const bad = getProperty(person, "email"); // Error: "email" not in Person
```
### Multiple Constraints
```typescript
interface Printable {
print(): void;
}
interface Loggable {
log(): string;
}
// T must satisfy both interfaces
function process<T extends Printable & Loggable>(item: T): void {
item.print();
console.log(item.log());
}
```
### Default Type Parameters
```typescript
interface ApiResponse<T = unknown, E = Error> {
data?: T;
error?: E;
status: number;
}
// Uses defaults
const response1: ApiResponse = { status: 200 };
// Override data type only
const response2: ApiResponse<User> = { data: user, status: 200 };
// Override both
const response3: ApiResponse<User, ValidationError> = {
error: new ValidationError(),
status: 400
};
```
---
## Mapped Types
### Basic Mapped Types
```typescript
// Transform all properties to optional
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Transform all properties to required
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Transform all properties to readonly
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Remove readonly modifier
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
```
### Practical Mapped Types
```typescript
// Make all properties nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Make all properties async getters
type AsyncGetters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => Promise<T[K]>;
};
interface User {
name: string;
email: string;
}
type UserGetters = AsyncGetters<User>;
// {
// getName: () => Promise<string>;
// getEmail: () => Promise<string>;
// }
```
### Key Remapping (as clause)
```typescript
// Filter keys by type
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
active: boolean;
score: number;
}
type StringProps = FilterByType<Mixed, string>;
// { name: string }
type NumberProps = FilterByType<Mixed, number>;
// { age: number; score: number }
// Prefix all keys
type Prefixed<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
type PrefixedUser = Prefixed<User, "user">;
// { userName: string; userEmail: string }
```
---
## Conditional Types
### Basic Conditional Types
```typescript
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true
// Practical: Extract non-nullable type
type NonNullable<T> = T extends null | undefined ? never : T;
type D = NonNullable<string | null>; // string
type E = NonNullable<number | undefined>; // number
```
### Distributive Conditional Types
```typescript
// Conditional types distribute over unions
type ToArray<T> = T extends unknown ? T[] : never;
type StringOrNumberArray = ToArray<string | number>;
// string[] | number[] (not (string | number)[])
// Prevent distribution with tuple
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type Mixed = ToArrayNonDist<string | number>;
// (string | number)[]
```
### infer Keyword
```typescript
// Extract return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type FnReturn = ReturnType<() => string>; // string
// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Element = ArrayElement<number[]>; // number
// Extract Promise result
type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
type Result = Awaited<Promise<Promise<string>>>; // string
// Extract function first parameter
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type First = FirstParam<(name: string, age: number) => void>; // string
```
### Practical Conditional Types
```typescript
// API response helper
type ApiResult<T> =
| { success: true; data: T }
| { success: false; error: string };
// Extract data type from result
type ExtractData<T> = T extends { success: true; data: infer D } ? D : never;
type UserResult = ApiResult<User>;
type UserData = ExtractData<UserResult>; // User
// Type-safe event handlers
type EventHandler<T> = T extends `on${infer Event}`
? (event: Event) => void
: never;
type ClickHandler = EventHandler<"onClick">; // (event: "Click") => void
```
---
## Template Literal Types
### Basic Template Literals
```typescript
type Greeting = `Hello, ${string}!`;
const valid: Greeting = "Hello, World!"; // OK
const invalid: Greeting = "Hi, World!"; // Error
// Combine with unions
type Size = "small" | "medium" | "large";
type Color = "red" | "blue" | "green";
type ColoredSize = `${Color}-${Size}`;
// "red-small" | "red-medium" | "red-large" |
// "blue-small" | "blue-medium" | "blue-large" |
// "green-small" | "green-medium" | "green-large"
```
### String Manipulation Types
```typescript
// Built-in string manipulation types
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
// Practical: Generate event names
type Event = "click" | "hover" | "focus";
type EventHandler = `on${Capitalize<Event>}`;
// "onClick" | "onHover" | "onFocus"
// CSS property with vendor prefixes
type CSSProp = "transform" | "transition";
type Prefixed = `-webkit-${CSSProp}` | `-moz-${CSSProp}` | CSSProp;
```
### Advanced Template Patterns
```typescript
// Parse dot-notation paths
type PathSegment<T> = T extends `${infer Head}.${infer Tail}`
? Head | PathSegment<Tail>
: T;
type Segments = PathSegment<"user.profile.name">;
// "user" | "profile" | "name"
// HTTP methods with paths
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "/users" | "/posts" | "/comments";
type Route = `${Method} ${Endpoint}`;
// "GET /users" | "GET /posts" | "GET /comments" |
// "POST /users" | ... etc
// Type-safe SQL column references
type Table = "users" | "posts";
type Column<T extends Table> = T extends "users"
? "id" | "name" | "email"
: T extends "posts"
? "id" | "title" | "content"
: never;
type UserColumn = `users.${Column<"users">}`;
// "users.id" | "users.name" | "users.email"
```
---
## Variadic Tuple Types
### Basic Variadic Tuples
```typescript
// Spread tuple types
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];
type Combined = Concat<[1, 2], [3, 4]>;
// [1, 2, 3, 4]
// Prepend element
type Prepend<T, U extends unknown[]> = [T, ...U];
type WithFirst = Prepend<0, [1, 2, 3]>;
// [0, 1, 2, 3]
// Append element
type Append<T extends unknown[], U> = [...T, U];
type WithLast = Append<[1, 2, 3], 4>;
// [1, 2, 3, 4]
```
### Practical Variadic Patterns
```typescript
// Typed curry function
type Curry<F> = F extends (...args: infer A) => infer R
? A extends [infer First, ...infer Rest]
? (arg: First) => Curry<(...args: Rest) => R>
: R
: never;
declare function curry<F extends (...args: any[]) => any>(fn: F): Curry<F>;
function add(a: number, b: number, c: number): number {
return a + b + c;
}
const curriedAdd = curry(add);
const add1 = curriedAdd(1); // (arg: number) => Curry<...>
const add1and2 = add1(2); // (arg: number) => number
const result = add1and2(3); // number (6)
// Typed pipe function
type Pipe<T extends unknown[], R> = T extends [infer First, ...infer Rest]
? First extends (arg: R) => infer Next
? Pipe<Rest, Next>
: never
: R;
function pipe<T extends ((arg: any) => any)[]>(
...fns: T
): (arg: Parameters<T[0]>[0]) => Pipe<T, Parameters<T[0]>[0]> {
return (arg) => fns.reduce((acc, fn) => fn(acc), arg);
}
const process = pipe(
(n: number) => n * 2,
(n: number) => n.toString(),
(s: string) => s.length
);
const length = process(5); // number (2 - length of "10")
```
---
## Built-in Utility Types Reference
| Utility | Purpose | Example |
|---------|---------|---------|
| `Partial<T>` | All properties optional | `Partial<User>` |
| `Required<T>` | All properties required | `Required<Partial<User>>` |
| `Readonly<T>` | All properties readonly | `Readonly<User>` |
| `Pick<T, K>` | Select properties | `Pick<User, "id" \| "name">` |
| `Omit<T, K>` | Exclude properties | `Omit<User, "password">` |
| `Record<K, V>` | Create object type | `Record<string, User>` |
| `Exclude<T, U>` | Remove union members | `Exclude<"a" \| "b", "a">` |
| `Extract<T, U>` | Keep union members | `Extract<"a" \| "b", "a">` |
| `NonNullable<T>` | Remove null/undefined | `NonNullable<string \| null>` |
| `Parameters<F>` | Function parameters | `Parameters<typeof fn>` |
| `ReturnType<F>` | Function return | `ReturnType<typeof fn>` |
| `ConstructorParameters<C>` | Constructor params | `ConstructorParameters<typeof Date>` |
| `InstanceType<C>` | Instance type | `InstanceType<typeof Date>` |
| `Awaited<T>` | Unwrap Promise | `Awaited<Promise<User>>` |
| `NoInfer<T>` | Prevent inference | `NoInfer<T>` (TS 5.4+) |
@@ -0,0 +1,591 @@
# NestJS Integration Reference
> **Load when:** User asks about NestJS with TypeScript, API development, DTOs, validation, authentication, or backend patterns.
Type-safe API development with NestJS 11+.
## Contents
- [Project Structure](#project-structure)
- [Controllers and Routes](#controllers-and-routes)
- [DTOs and Validation](#dtos-and-validation)
- [Services and Dependency Injection](#services-and-dependency-injection)
- [Authentication](#authentication)
- [Error Handling](#error-handling)
---
## Project Structure
### Recommended Layout
```
src/
├── main.ts # Application entry point
├── app.module.ts # Root module
├── common/ # Shared utilities
│ ├── decorators/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/ # Configuration
│ ├── config.module.ts
│ └── env.validation.ts
└── modules/
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── users.repository.ts
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ ├── entities/
│ │ └── user.entity.ts
│ └── __tests__/
└── auth/
└── ...
```
### Module Configuration
```typescript
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UsersRepository } from './users.repository';
@Module({
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService] // Export for use in other modules
})
export class UsersModule {}
```
---
## Controllers and Routes
### Basic Controller
```typescript
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
Query,
HttpCode,
HttpStatus
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto, UpdateUserDto, UserResponseDto } from './dto';
import { PaginationDto } from '@/common/dto';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@ApiOperation({ summary: 'Get all users' })
@ApiResponse({ status: 200, type: [UserResponseDto] })
async findAll(@Query() query: PaginationDto): Promise<UserResponseDto[]> {
return this.usersService.findAll(query);
}
@Get(':id')
@ApiOperation({ summary: 'Get user by ID' })
@ApiResponse({ status: 200, type: UserResponseDto })
@ApiResponse({ status: 404, description: 'User not found' })
async findOne(@Param('id') id: string): Promise<UserResponseDto> {
return this.usersService.findOne(id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create new user' })
@ApiResponse({ status: 201, type: UserResponseDto })
async create(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
return this.usersService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: 'Update user' })
async update(
@Param('id') id: string,
@Body() dto: UpdateUserDto
): Promise<UserResponseDto> {
return this.usersService.update(id, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete user' })
async remove(@Param('id') id: string): Promise<void> {
return this.usersService.remove(id);
}
}
```
---
## DTOs and Validation
### Class-Validator Approach
```typescript
// dto/create-user.dto.ts
import {
IsString,
IsEmail,
MinLength,
MaxLength,
IsOptional,
IsEnum,
ValidateNested,
IsArray
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export enum UserRole {
User = 'user',
Admin = 'admin',
Moderator = 'moderator'
}
export class AddressDto {
@ApiProperty()
@IsString()
street: string;
@ApiProperty()
@IsString()
city: string;
@ApiProperty()
@IsString()
country: string;
}
export class CreateUserDto {
@ApiProperty({ example: 'john@example.com' })
@IsEmail()
email: string;
@ApiProperty({ minLength: 2, maxLength: 50 })
@IsString()
@MinLength(2)
@MaxLength(50)
name: string;
@ApiProperty({ minLength: 8 })
@IsString()
@MinLength(8)
password: string;
@ApiPropertyOptional({ enum: UserRole, default: UserRole.User })
@IsOptional()
@IsEnum(UserRole)
role?: UserRole = UserRole.User;
@ApiPropertyOptional({ type: AddressDto })
@IsOptional()
@ValidateNested()
@Type(() => AddressDto)
address?: AddressDto;
@ApiPropertyOptional({ type: [String] })
@IsOptional()
@IsArray()
@IsString({ each: true })
tags?: string[];
}
// dto/update-user.dto.ts
import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
```
### Zod-Based DTOs (Modern Approach)
```typescript
// dto/user.schema.ts
import { z } from 'zod';
import { createZodDto } from 'nestjs-zod';
// Define Zod schemas
export const UserRoleSchema = z.enum(['user', 'admin', 'moderator']);
export const AddressSchema = z.object({
street: z.string(),
city: z.string(),
country: z.string()
});
export const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(50),
password: z.string().min(8),
role: UserRoleSchema.default('user').optional(),
address: AddressSchema.optional(),
tags: z.array(z.string()).optional()
});
export const UpdateUserSchema = CreateUserSchema.partial();
// Create DTO classes from schemas
export class CreateUserDto extends createZodDto(CreateUserSchema) {}
export class UpdateUserDto extends createZodDto(UpdateUserSchema) {}
// Infer types
export type CreateUser = z.infer<typeof CreateUserSchema>;
export type UpdateUser = z.infer<typeof UpdateUserSchema>;
```
### Global Validation Pipe
```typescript
// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Throw on unknown properties
transform: true, // Transform payloads to DTO classes
transformOptions: {
enableImplicitConversion: true
}
})
);
await app.listen(3000);
}
bootstrap();
```
---
## Services and Dependency Injection
### Typed Service
```typescript
// users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { UsersRepository } from './users.repository';
import { CreateUserDto, UpdateUserDto, UserResponseDto } from './dto';
import { PaginationDto } from '@/common/dto';
@Injectable()
export class UsersService {
constructor(private readonly usersRepository: UsersRepository) {}
async findAll(query: PaginationDto): Promise<UserResponseDto[]> {
const users = await this.usersRepository.findAll(query);
return users.map(this.toResponseDto);
}
async findOne(id: string): Promise<UserResponseDto> {
const user = await this.usersRepository.findById(id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return this.toResponseDto(user);
}
async create(dto: CreateUserDto): Promise<UserResponseDto> {
const user = await this.usersRepository.create(dto);
return this.toResponseDto(user);
}
async update(id: string, dto: UpdateUserDto): Promise<UserResponseDto> {
const user = await this.usersRepository.update(id, dto);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return this.toResponseDto(user);
}
async remove(id: string): Promise<void> {
const deleted = await this.usersRepository.delete(id);
if (!deleted) {
throw new NotFoundException(`User with ID ${id} not found`);
}
}
private toResponseDto(user: User): UserResponseDto {
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
createdAt: user.createdAt
};
}
}
```
### Repository Pattern
```typescript
// users.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { User, Prisma } from '@prisma/client';
import { PaginationDto } from '@/common/dto';
@Injectable()
export class UsersRepository {
constructor(private readonly prisma: PrismaService) {}
async findAll(query: PaginationDto): Promise<User[]> {
return this.prisma.user.findMany({
skip: query.skip,
take: query.take,
orderBy: { createdAt: 'desc' }
});
}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { email } });
}
async create(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({ data });
}
async update(id: string, data: Prisma.UserUpdateInput): Promise<User | null> {
try {
return await this.prisma.user.update({ where: { id }, data });
} catch {
return null;
}
}
async delete(id: string): Promise<boolean> {
try {
await this.prisma.user.delete({ where: { id } });
return true;
} catch {
return false;
}
}
}
```
---
## Authentication
### JWT Authentication
```typescript
// auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '@/modules/users/users.service';
interface JwtPayload {
sub: string;
email: string;
role: string;
iat: number;
exp: number;
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
configService: ConfigService,
private readonly usersService: UsersService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.getOrThrow<string>('JWT_SECRET')
});
}
async validate(payload: JwtPayload) {
const user = await this.usersService.findOne(payload.sub);
if (!user) {
throw new UnauthorizedException();
}
return { id: payload.sub, email: payload.email, role: payload.role };
}
}
```
### Role-Based Access Control
```typescript
// common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()]
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
// Usage in controller
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('users')
@Roles('admin')
findAllUsers() {
return this.adminService.findAllUsers();
}
@Delete('users/:id')
@Roles('admin', 'moderator')
removeUser(@Param('id') id: string) {
return this.adminService.removeUser(id);
}
}
```
---
## Error Handling
### Exception Filters
```typescript
// common/filters/http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus
} from '@nestjs/common';
import { Response } from 'express';
interface ErrorResponse {
statusCode: number;
message: string;
error: string;
timestamp: string;
path: string;
}
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
let error = 'Internal Server Error';
if (exception instanceof HttpException) {
status = exception.getStatus();
const exceptionResponse = exception.getResponse();
if (typeof exceptionResponse === 'string') {
message = exceptionResponse;
} else if (typeof exceptionResponse === 'object') {
const responseObj = exceptionResponse as Record<string, unknown>;
message = (responseObj.message as string) || message;
error = (responseObj.error as string) || exception.name;
}
}
const errorResponse: ErrorResponse = {
statusCode: status,
message,
error,
timestamp: new Date().toISOString(),
path: request.url
};
response.status(status).json(errorResponse);
}
}
// main.ts
app.useGlobalFilters(new AllExceptionsFilter());
```
### Custom Exceptions
```typescript
// common/exceptions/business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class BusinessException extends HttpException {
constructor(
message: string,
public readonly code: string,
status: HttpStatus = HttpStatus.BAD_REQUEST
) {
super({ message, code }, status);
}
}
export class InsufficientFundsException extends BusinessException {
constructor(required: number, available: number) {
super(
`Insufficient funds: required ${required}, available ${available}`,
'INSUFFICIENT_FUNDS'
);
}
}
export class DuplicateEmailException extends BusinessException {
constructor(email: string) {
super(`Email ${email} is already registered`, 'DUPLICATE_EMAIL');
}
}
// Usage
throw new InsufficientFundsException(100, 50);
```
@@ -0,0 +1,618 @@
# React Integration Reference
> **Load when:** User asks about React with TypeScript, typed components, hooks, state management, or React patterns.
Type-safe React development patterns for React 19+.
## Contents
- [Component Patterns](#component-patterns)
- [Hooks with TypeScript](#hooks-with-typescript)
- [State Management](#state-management)
- [Event Handling](#event-handling)
- [Context API](#context-api)
---
## Component Patterns
### Functional Components
```typescript
// Basic typed component
interface GreetingProps {
name: string;
age?: number;
}
function Greeting({ name, age }: GreetingProps) {
return (
<div>
Hello, {name}!
{age && <span> You are {age} years old.</span>}
</div>
);
}
// With React.FC (optional, some prefer explicit return type)
const GreetingFC: React.FC<GreetingProps> = ({ name, age }) => {
return <div>Hello, {name}!</div>;
};
// Component with children
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
```
### Generic Components
```typescript
// Generic list component
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// Usage
interface User {
id: string;
name: string;
}
<List<User>
items={users}
keyExtractor={(user) => user.id}
renderItem={(user) => <span>{user.name}</span>}
/>;
// Generic select component
interface SelectProps<T> {
options: T[];
value: T | null;
onChange: (value: T) => void;
getLabel: (option: T) => string;
getValue: (option: T) => string;
}
function Select<T>({
options,
value,
onChange,
getLabel,
getValue
}: SelectProps<T>) {
return (
<select
value={value ? getValue(value) : ""}
onChange={(e) => {
const selected = options.find((opt) => getValue(opt) === e.target.value);
if (selected) onChange(selected);
}}
>
<option value="">Select...</option>
{options.map((opt) => (
<option key={getValue(opt)} value={getValue(opt)}>
{getLabel(opt)}
</option>
))}
</select>
);
}
```
### Polymorphic Components
```typescript
// Component that can render as different elements
type ButtonProps<T extends React.ElementType> = {
as?: T;
children: React.ReactNode;
variant?: "primary" | "secondary";
} & Omit<React.ComponentPropsWithoutRef<T>, "as" | "children">;
function Button<T extends React.ElementType = "button">({
as,
children,
variant = "primary",
...props
}: ButtonProps<T>) {
const Component = as || "button";
return (
<Component className={`btn btn-${variant}`} {...props}>
{children}
</Component>
);
}
// Usage
<Button>Click me</Button>
<Button as="a" href="/about">Link Button</Button>
<Button as={Link} to="/home">Router Link</Button>
```
---
## Hooks with TypeScript
### useState
```typescript
// Basic usage (type inferred)
const [count, setCount] = useState(0);
// Explicit type (for complex types or initial null)
const [user, setUser] = useState<User | null>(null);
// With union types
type Status = "idle" | "loading" | "success" | "error";
const [status, setStatus] = useState<Status>("idle");
// Lazy initialization
const [state, setState] = useState<ExpensiveState>(() => {
return computeExpensiveInitialState();
});
```
### useRef
```typescript
// DOM element ref
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current?.focus();
}
// Mutable ref (no initial render)
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
intervalRef.current = setInterval(() => {}, 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);
// Ref to store previous value
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
```
### useReducer
```typescript
// Define state and actions
interface CounterState {
count: number;
step: number;
}
type CounterAction =
| { type: "increment" }
| { type: "decrement" }
| { type: "setStep"; payload: number }
| { type: "reset" };
function counterReducer(
state: CounterState,
action: CounterAction
): CounterState {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "setStep":
return { ...state, step: action.payload };
case "reset":
return { count: 0, step: 1 };
}
}
// Usage
const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 });
dispatch({ type: "increment" });
dispatch({ type: "setStep", payload: 5 });
```
### Custom Hooks
```typescript
// Async data fetching hook
interface UseAsyncResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
function useAsync<T>(
asyncFn: () => Promise<T>,
deps: React.DependencyList = []
): UseAsyncResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const execute = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await asyncFn();
setData(result);
} catch (e) {
setError(e instanceof Error ? e : new Error(String(e)));
} finally {
setLoading(false);
}
}, deps);
useEffect(() => {
execute();
}, [execute]);
return { data, loading, error, refetch: execute };
}
// Usage
const { data: users, loading, error } = useAsync(
() => fetch("/api/users").then((r) => r.json()),
[]
);
// Local storage hook
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value: T | ((prev: T) => T)) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue];
}
```
---
## State Management
### Zustand with TypeScript
```typescript
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
// Define state interface
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
setUser: (user: User) => void;
}
// Create typed store
const useAuthStore = create<AuthState>()(
devtools(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
const response = await api.login(email, password);
set({
user: response.user,
token: response.token,
isAuthenticated: true
});
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false });
},
setUser: (user) => set({ user })
}),
{ name: 'auth-storage' }
)
)
);
// Usage with selectors
const user = useAuthStore((state) => state.user);
const login = useAuthStore((state) => state.login);
// Shallow comparison for multiple values
import { useShallow } from 'zustand/react/shallow';
const { user, isAuthenticated } = useAuthStore(
useShallow((state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated
}))
);
```
### Redux Toolkit with TypeScript
```typescript
import { createSlice, PayloadAction, configureStore } from '@reduxjs/toolkit';
// Define slice state
interface TodosState {
items: Todo[];
filter: "all" | "active" | "completed";
loading: boolean;
}
const initialState: TodosState = {
items: [],
filter: "all",
loading: false
};
// Create typed slice
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.items.push({
id: crypto.randomUUID(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action: PayloadAction<string>) => {
const todo = state.items.find((t) => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
setFilter: (state, action: PayloadAction<TodosState["filter"]>) => {
state.filter = action.payload;
}
}
});
// Configure store with type inference
const store = configureStore({
reducer: {
todos: todosSlice.reducer
}
});
// Infer types from store
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
// Typed hooks
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
const useAppDispatch = () => useDispatch<AppDispatch>();
const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// Usage
const todos = useAppSelector((state) => state.todos.items);
const dispatch = useAppDispatch();
dispatch(todosSlice.actions.addTodo("New todo"));
```
---
## Event Handling
### Common Event Types
```typescript
// Click events
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
console.log(event.currentTarget.name);
}
// Form events
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
}
// Input change
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const { name, value, checked, type } = event.target;
const inputValue = type === "checkbox" ? checked : value;
}
// Keyboard events
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter") {
event.preventDefault();
// submit form
}
}
// Focus events
function handleFocus(event: React.FocusEvent<HTMLInputElement>) {
event.target.select();
}
// Drag events
function handleDrag(event: React.DragEvent<HTMLDivElement>) {
event.dataTransfer.setData("text/plain", "dragged data");
}
```
### Form with TypeScript
```typescript
interface FormData {
name: string;
email: string;
role: "user" | "admin";
}
function RegistrationForm() {
const [formData, setFormData] = useState<FormData>({
name: "",
email: "",
role: "user"
});
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
/>
<select name="role" value={formData.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Register</button>
</form>
);
}
```
---
## Context API
### Typed Context
```typescript
// Define context type
interface ThemeContextType {
theme: "light" | "dark";
toggleTheme: () => void;
}
// Create context with undefined default
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Provider component
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
const toggleTheme = useCallback(() => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
}, []);
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook with type safety
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
// Usage
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return <button onClick={toggleTheme}>Current: {theme}</button>;
}
```
### Generic Context Factory
```typescript
// Factory function for creating typed contexts
function createContext<T>(displayName: string) {
const Context = React.createContext<T | undefined>(undefined);
Context.displayName = displayName;
function useContextHook(): T {
const context = React.useContext(Context);
if (context === undefined) {
throw new Error(`use${displayName} must be used within ${displayName}Provider`);
}
return context;
}
return [Context.Provider, useContextHook] as const;
}
// Usage
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const [AuthProvider, useAuth] = createContext<AuthContextType>("Auth");
```
@@ -0,0 +1,546 @@
# Modern Toolchain Reference
> **Load when:** User asks about Vite, pnpm, ESLint, Vitest, tsconfig, build tools, or project configuration.
Modern TypeScript toolchain configuration for 2025.
## Contents
- [TypeScript Configuration](#typescript-configuration)
- [Package Manager (pnpm)](#package-manager-pnpm)
- [Build Tool (Vite)](#build-tool-vite)
- [Linting (ESLint 9)](#linting-eslint-9)
- [Testing (Vitest)](#testing-vitest)
- [Formatting (Prettier)](#formatting-prettier)
---
## TypeScript Configuration
### Strict Enterprise Configuration
```json
// tsconfig.json
{
"compilerOptions": {
// Language and Environment
"target": "ES2024",
"lib": ["ES2024"],
"module": "ESNext",
"moduleResolution": "bundler",
// Strict Type Checking
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
// Module Handling
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
// Output
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
// Path Aliases
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// Performance
"skipLibCheck": true,
"incremental": true,
"tsBuildInfoFile": "./node_modules/.cache/tsbuildinfo"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
### React Project Configuration
```json
// tsconfig.json for React
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
```
### Node.js Backend Configuration
```json
// tsconfig.json for Node.js/NestJS
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
```
---
## Package Manager (pnpm)
### Why pnpm
| Feature | npm | pnpm |
|---------|-----|------|
| Disk usage | Duplicates packages | Shared store, symlinks |
| Install speed | Slower | 2-3x faster |
| Strictness | Allows phantom deps | Strict by default |
| Monorepo support | Basic workspaces | First-class support |
### Basic Commands
```bash
# Install dependencies
pnpm install
# Add packages
pnpm add typescript
pnpm add -D vitest @types/node
# Run scripts
pnpm run build
pnpm test
# Update packages
pnpm update
pnpm update --interactive
# List packages
pnpm list
pnpm why lodash
# Clean install
pnpm install --frozen-lockfile
```
### Workspace Configuration
```yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
```
```json
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "pnpm -r run build",
"test": "pnpm -r run test",
"lint": "pnpm -r run lint"
}
}
```
---
## Build Tool (Vite)
### Vite Configuration
```typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
react(),
tsconfigPaths()
],
server: {
port: 3000,
host: true
},
build: {
target: 'es2022',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash-es', 'date-fns']
}
}
}
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts']
}
});
```
### Library Mode
```typescript
// vite.config.ts for library
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
export default defineConfig({
build: {
lib: {
entry: './src/index.ts',
name: 'MyLibrary',
fileName: 'my-library',
formats: ['es', 'cjs']
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
},
plugins: [
dts({ insertTypesEntry: true })
]
});
```
---
## Linting (ESLint 9)
### Flat Config Format
```javascript
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
export default tseslint.config(
// Base ESLint recommendations
eslint.configs.recommended,
// TypeScript strict type-checking
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
// TypeScript parser options
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
}
},
// React configuration
{
files: ['**/*.tsx'],
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
'react/react-in-jsx-scope': 'off'
},
settings: {
react: { version: 'detect' }
}
},
// Custom rules
{
rules: {
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports'
}],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/await-thenable': 'error'
}
},
// Ignore patterns
{
ignores: ['dist/**', 'node_modules/**', '*.config.js']
}
);
```
### Common Rules Explained
```javascript
// Important TypeScript ESLint rules
{
rules: {
// Enforce type imports for better tree-shaking
'@typescript-eslint/consistent-type-imports': 'error',
// Prevent unhandled promises
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
// Prevent awaiting non-promises
'@typescript-eslint/await-thenable': 'error',
// Require return types on functions
'@typescript-eslint/explicit-function-return-type': ['error', {
allowExpressions: true
}],
// Prefer nullish coalescing
'@typescript-eslint/prefer-nullish-coalescing': 'error',
// Prefer optional chaining
'@typescript-eslint/prefer-optional-chain': 'error',
// No any type
'@typescript-eslint/no-explicit-any': 'error',
// Enforce strict boolean expressions
'@typescript-eslint/strict-boolean-expressions': 'error'
}
}
```
---
## Testing (Vitest)
### Vitest Configuration
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.d.ts', '**/*.config.*', '**/test/**']
},
typecheck: {
enabled: true
}
}
});
```
### Test Setup
```typescript
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { afterEach, beforeAll, afterAll, vi } from 'vitest';
// Cleanup after each test
afterEach(() => {
cleanup();
});
// Mock environment variables
beforeAll(() => {
vi.stubEnv('API_URL', 'http://localhost:3000');
});
afterAll(() => {
vi.unstubAllEnvs();
});
```
### Example Tests
```typescript
// src/utils/format.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency, formatDate } from './format';
describe('formatCurrency', () => {
it('formats USD correctly', () => {
expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
});
it('handles zero', () => {
expect(formatCurrency(0, 'USD')).toBe('$0.00');
});
});
// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Button } from './Button';
describe('Button', () => {
it('renders with label', () => {
render(<Button label="Click me" onClick={() => {}} />);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
});
```
---
## Formatting (Prettier)
### Prettier Configuration
```json
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
```
### Integration with ESLint
```javascript
// eslint.config.js
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
// ... other configs
eslintConfigPrettier // Must be last to disable conflicting rules
);
```
### Package Scripts
```json
// package.json
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"typecheck": "tsc --noEmit"
}
}
```
---
## Complete Project Setup
### Quick Start Script
```bash
#!/bin/bash
# setup-ts-project.sh
PROJECT_NAME=${1:-my-app}
# Create project with Vite
pnpm create vite@latest $PROJECT_NAME --template react-ts
cd $PROJECT_NAME
# Install dependencies
pnpm install
# Add development dependencies
pnpm add -D \
typescript-eslint \
@eslint/js \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-config-prettier \
prettier \
vitest \
@vitest/coverage-v8 \
@testing-library/react \
@testing-library/jest-dom \
vite-tsconfig-paths
echo "Project setup complete! Run: cd $PROJECT_NAME && pnpm dev"
```
@@ -0,0 +1,478 @@
# TypeScript Type System Reference
> **Load when:** User asks about type annotations, interfaces vs types, unions, intersections, or type system fundamentals.
Complete guide to TypeScript's structural type system.
## Contents
- [Type Annotations](#type-annotations)
- [Interfaces vs Type Aliases](#interfaces-vs-type-aliases)
- [Union and Intersection Types](#union-and-intersection-types)
- [Literal Types](#literal-types)
- [Type Guards and Narrowing](#type-guards-and-narrowing)
- [The satisfies Operator](#the-satisfies-operator)
---
## Type Annotations
### Variable Annotations
```typescript
// Explicit type annotations
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
// Type inference (preferred when obvious)
const inferredName = "Bob"; // TypeScript infers string
const inferredAge = 25; // TypeScript infers number
// Arrays
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ["a", "b", "c"];
// Tuples (fixed-length arrays with specific types)
const pair: [string, number] = ["age", 30];
const triple: [string, number, boolean] = ["name", 1, true];
```
### Function Annotations
```typescript
// Function with typed parameters and return
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function
const add = (a: number, b: number): number => a + b;
// Optional parameters
function greetOptional(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}!`;
}
// Default parameters
function greetDefault(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
// Function type alias
type Comparator<T> = (a: T, b: T) => number;
const numberCompare: Comparator<number> = (a, b) => a - b;
```
---
## Interfaces vs Type Aliases
### When to Use Interfaces
```typescript
// Interfaces are ideal for object shapes
interface User {
id: string;
name: string;
email: string;
}
// Interfaces can be extended
interface Employee extends User {
employeeId: string;
department: string;
}
// Interfaces can be implemented by classes
class Manager implements Employee {
constructor(
public id: string,
public name: string,
public email: string,
public employeeId: string,
public department: string
) {}
}
// Declaration merging (interfaces only)
interface Config {
apiUrl: string;
}
interface Config {
timeout: number;
}
// Config now has both apiUrl and timeout
```
### When to Use Type Aliases
```typescript
// Type aliases for unions
type Status = "pending" | "approved" | "rejected";
// Type aliases for complex types
type Handler = (event: Event) => void;
// Type aliases for mapped types
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Type aliases for conditional types
type NonNullable<T> = T extends null | undefined ? never : T;
// Type aliases for tuples
type Point = [x: number, y: number];
type RGB = [red: number, green: number, blue: number];
```
### Decision Guide
| Use Case | Prefer |
|----------|--------|
| Object shapes | `interface` |
| Extending objects | `interface` |
| Class contracts | `interface` |
| Union types | `type` |
| Tuple types | `type` |
| Mapped types | `type` |
| Conditional types | `type` |
| Primitive aliases | `type` |
---
## Union and Intersection Types
### Union Types
```typescript
// Value can be one of several types
type StringOrNumber = string | number;
// Discriminated unions (tagged unions)
interface Dog {
kind: "dog";
bark(): void;
}
interface Cat {
kind: "cat";
meow(): void;
}
type Pet = Dog | Cat;
function speak(pet: Pet): void {
switch (pet.kind) {
case "dog":
pet.bark();
break;
case "cat":
pet.meow();
break;
}
}
```
### Intersection Types
```typescript
// Value must satisfy all types
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
type Person = HasName & HasAge;
const person: Person = {
name: "Alice",
age: 30
};
// Practical: Extending with additional properties
type WithTimestamp<T> = T & { createdAt: Date; updatedAt: Date };
interface Article {
title: string;
content: string;
}
type TimestampedArticle = WithTimestamp<Article>;
```
---
## Literal Types
### String Literals
```typescript
// Specific string values
type Direction = "north" | "south" | "east" | "west";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
move("north"); // OK
move("up"); // Error: Argument of type '"up"' is not assignable
```
### Numeric Literals
```typescript
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type BinaryDigit = 0 | 1;
function roll(): DiceRoll {
return Math.ceil(Math.random() * 6) as DiceRoll;
}
```
### Template Literal Types
```typescript
// Construct string literal types
type EventName = "click" | "hover" | "focus";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onHover" | "onFocus"
// CSS unit types
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px"; // OK
const height: CSSValue = "50%"; // OK
const bad: CSSValue = "100"; // Error
```
---
## Type Guards and Narrowing
### Built-in Type Guards
```typescript
function process(value: string | number | null): string {
// typeof guard
if (typeof value === "string") {
return value.toUpperCase();
}
// typeof guard for number
if (typeof value === "number") {
return value.toFixed(2);
}
// null/undefined narrowing
if (value === null) {
return "null";
}
// Exhaustiveness check
const _exhaustive: never = value;
throw new Error(`Unhandled case: ${_exhaustive}`);
}
```
### instanceof Guard
```typescript
class ApiError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
}
}
class ValidationError extends Error {
constructor(public fields: string[]) {
super("Validation failed");
}
}
function handleError(error: Error): void {
if (error instanceof ApiError) {
console.log(`API Error ${error.statusCode}: ${error.message}`);
} else if (error instanceof ValidationError) {
console.log(`Validation Error on fields: ${error.fields.join(", ")}`);
} else {
console.log(`Unknown error: ${error.message}`);
}
}
```
### Custom Type Guards
```typescript
interface User {
type: "user";
name: string;
}
interface Admin {
type: "admin";
name: string;
permissions: string[];
}
type Account = User | Admin;
// Type predicate: returns boolean but narrows type
function isAdmin(account: Account): account is Admin {
return account.type === "admin";
}
function getPermissions(account: Account): string[] {
if (isAdmin(account)) {
return account.permissions; // TypeScript knows this is Admin
}
return [];
}
```
### Assertion Functions
```typescript
function assertDefined<T>(value: T | undefined, message: string): asserts value is T {
if (value === undefined) {
throw new Error(message);
}
}
function processUser(user: User | undefined): void {
assertDefined(user, "User is required");
// After assertion, user is narrowed to User
console.log(user.name);
}
```
---
## The satisfies Operator
### Problem: Type Assertions Hide Bugs
```typescript
// Using 'as' can hide type errors
const config = {
port: 3000,
host: "localhost"
} as Record<string, string | number>;
// No error, but port is now string | number
const portString = config.port.toFixed(2); // Runtime error if port is string!
```
### Solution: satisfies Validates Without Widening
```typescript
// satisfies checks conformance but preserves literal types
const config = {
port: 3000,
host: "localhost"
} satisfies Record<string, string | number>;
// TypeScript knows port is number, host is string
config.port.toFixed(2); // OK - port is number
config.host.toUpperCase(); // OK - host is string
```
### Practical Use Cases
```typescript
// Color palette with constrained values
const palette = {
primary: "#007bff",
secondary: "#6c757d",
success: "#28a745"
} satisfies Record<string, `#${string}`>;
// TypeScript knows each property exists and is a hex string
palette.primary.startsWith("#"); // OK
// Route configuration
type RouteConfig = {
path: string;
method: "GET" | "POST";
handler: () => void;
};
const routes = {
home: { path: "/", method: "GET", handler: () => {} },
login: { path: "/login", method: "POST", handler: () => {} }
} satisfies Record<string, RouteConfig>;
// TypeScript preserves literal types for each route
routes.home.method; // "GET" (not "GET" | "POST")
```
---
## Special Types
### any vs unknown
```typescript
// any: Opt out of type checking (avoid)
let anyValue: any = "hello";
anyValue.toFixed(2); // No error, but crashes at runtime
// unknown: Type-safe any (prefer)
let unknownValue: unknown = "hello";
unknownValue.toFixed(2); // Error: Object is of type 'unknown'
// Must narrow unknown before use
if (typeof unknownValue === "string") {
unknownValue.toUpperCase(); // OK after narrowing
}
```
### never
```typescript
// never: Represents impossible values
function fail(message: string): never {
throw new Error(message);
}
// Exhaustiveness checking with never
type Shape = "circle" | "square";
function getArea(shape: Shape): number {
switch (shape) {
case "circle":
return Math.PI;
case "square":
return 1;
default:
// If we add a new shape, this will error
const _exhaustive: never = shape;
throw new Error(`Unknown shape: ${_exhaustive}`);
}
}
```
### void vs undefined
```typescript
// void: Function doesn't return anything meaningful
function log(message: string): void {
console.log(message);
}
// undefined: Explicit undefined value
function findUser(id: string): User | undefined {
return users.get(id);
}
```
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env bash
#
# TypeScript Project Setup Validator
#
# Checks that TypeScript project is properly configured for development.
# Run before starting development or deploying.
#
# Usage: ./validate-setup.sh
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
ERRORS=0
WARNINGS=0
pass() { echo -e "${GREEN}${NC} $1"; }
fail() { echo -e "${RED}${NC} $1"; ((ERRORS++)); }
warn() { echo -e "${YELLOW}!${NC} $1"; ((WARNINGS++)); }
info() { echo -e "${BLUE}${NC} $1"; }
echo "=========================================="
echo "TypeScript Project Setup Validator"
echo "=========================================="
echo ""
# Check 1: Node.js version
echo "Checking Node.js..."
if command -v node &> /dev/null; then
NODE_VERSION=$(node -v | sed 's/v//')
MAJOR_VERSION=$(echo $NODE_VERSION | cut -d. -f1)
if [[ $MAJOR_VERSION -ge 20 ]]; then
pass "Node.js $NODE_VERSION (recommended: 20+)"
else
warn "Node.js $NODE_VERSION (recommended: 20+ for full ES2024 support)"
fi
else
fail "Node.js not found"
fi
# Check 2: Package manager
echo ""
echo "Checking package manager..."
if command -v pnpm &> /dev/null; then
PNPM_VERSION=$(pnpm -v)
pass "pnpm $PNPM_VERSION installed (recommended)"
elif command -v npm &> /dev/null; then
NPM_VERSION=$(npm -v)
warn "npm $NPM_VERSION installed (pnpm recommended for better performance)"
else
fail "No package manager found"
fi
# Check 3: TypeScript installation
echo ""
echo "Checking TypeScript..."
if [[ -f "node_modules/typescript/package.json" ]]; then
TS_VERSION=$(node -p "require('./node_modules/typescript/package.json').version")
MAJOR_VERSION=$(echo $TS_VERSION | cut -d. -f1)
MINOR_VERSION=$(echo $TS_VERSION | cut -d. -f2)
if [[ $MAJOR_VERSION -ge 5 ]] && [[ $MINOR_VERSION -ge 5 ]]; then
pass "TypeScript $TS_VERSION installed"
else
warn "TypeScript $TS_VERSION installed (5.5+ recommended)"
fi
elif command -v tsc &> /dev/null; then
TS_VERSION=$(tsc --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
pass "TypeScript $TS_VERSION (global)"
else
fail "TypeScript not found"
fi
# Check 4: tsconfig.json
echo ""
echo "Checking TypeScript configuration..."
if [[ -f "tsconfig.json" ]]; then
pass "tsconfig.json exists"
# Check for strict mode
if grep -q '"strict":\s*true' tsconfig.json 2>/dev/null; then
pass "Strict mode enabled"
else
warn "Strict mode not enabled (recommended)"
fi
# Check for target
if grep -qE '"target":\s*"ES202[234]"' tsconfig.json 2>/dev/null; then
pass "Modern ES target configured"
else
info "Consider updating target to ES2024"
fi
else
fail "tsconfig.json not found"
fi
# Check 5: package.json type field
echo ""
echo "Checking ESM configuration..."
if [[ -f "package.json" ]]; then
if grep -q '"type":\s*"module"' package.json 2>/dev/null; then
pass "ESM mode enabled (type: module)"
else
info "Consider adding \"type\": \"module\" for ESM"
fi
else
fail "package.json not found"
fi
# Check 6: ESLint configuration
echo ""
echo "Checking linting setup..."
if [[ -f "eslint.config.js" ]] || [[ -f "eslint.config.mjs" ]]; then
pass "ESLint flat config found"
elif [[ -f ".eslintrc.js" ]] || [[ -f ".eslintrc.json" ]]; then
warn "Legacy ESLint config found (migrate to flat config for ESLint 9+)"
else
warn "ESLint not configured"
fi
# Check 7: Prettier configuration
echo ""
echo "Checking formatting setup..."
if [[ -f ".prettierrc" ]] || [[ -f ".prettierrc.json" ]] || [[ -f "prettier.config.js" ]]; then
pass "Prettier configured"
else
info "Consider adding Prettier for consistent formatting"
fi
# Check 8: Test framework
echo ""
echo "Checking test setup..."
if [[ -f "vitest.config.ts" ]] || [[ -f "vitest.config.js" ]]; then
pass "Vitest configured"
elif [[ -f "jest.config.ts" ]] || [[ -f "jest.config.js" ]]; then
pass "Jest configured"
elif grep -q '"vitest"' package.json 2>/dev/null; then
pass "Vitest in dependencies"
elif grep -q '"jest"' package.json 2>/dev/null; then
pass "Jest in dependencies"
else
warn "No test framework detected"
fi
# Check 9: Git hooks (optional)
echo ""
echo "Checking Git hooks..."
if [[ -d ".husky" ]]; then
pass "Husky Git hooks configured"
elif [[ -f ".git/hooks/pre-commit" ]]; then
pass "Git pre-commit hook exists"
else
info "Consider adding pre-commit hooks for quality checks"
fi
# Check 10: Dependencies up to date
echo ""
echo "Checking for outdated packages..."
if command -v pnpm &> /dev/null && [[ -f "pnpm-lock.yaml" ]]; then
OUTDATED=$(pnpm outdated --format json 2>/dev/null | grep -c '"' || echo "0")
if [[ "$OUTDATED" == "0" ]] || [[ "$OUTDATED" == "" ]]; then
pass "All packages up to date"
else
info "Some packages may be outdated (run: pnpm outdated)"
fi
elif command -v npm &> /dev/null && [[ -f "package-lock.json" ]]; then
info "Run 'npm outdated' to check for updates"
fi
# Summary
echo ""
echo "=========================================="
echo "Summary"
echo "=========================================="
if [[ $ERRORS -eq 0 ]] && [[ $WARNINGS -eq 0 ]]; then
echo -e "${GREEN}All checks passed!${NC}"
echo "Your TypeScript project is properly configured."
exit 0
elif [[ $ERRORS -eq 0 ]]; then
echo -e "${YELLOW}Passed with $WARNINGS warning(s)${NC}"
echo "Project is functional but could be improved."
exit 0
else
echo -e "${RED}Failed with $ERRORS error(s) and $WARNINGS warning(s)${NC}"
echo "Fix errors before proceeding."
exit 1
fi
+142
View File
@@ -0,0 +1,142 @@
---
name: serilog-log-analyzer
description: |
Expert Serilog JSON log analyst for OTCryptoRobot.
MUST BE USED for analyzing BE (RobotCBBE) and FE (RobotFE) logs.
Reads CLEF (Compact Log Event Format) JSON logs written by Serilog.
Log file locations:
- BE JSON logs: `RobotCBBE/Logs/CryptoRobot_Log-{date}.json` (CompactJsonFormatter, min level: Information)
- BE text logs: `RobotCBBE/Logs/log-{date}.txt` (all levels including Verbose)
- FE logs: `RobotFE/Logs/`
Examples:
- <example>
Context: Investigating errors
user: "Check today's logs for errors"
assistant: "I'll use @serilog-log-analyzer to analyze the logs"
<commentary>
Reads JSON log, groups errors, finds patterns.
</commentary>
</example>
- <example>
Context: Auth failures
user: "Why am I getting 401 errors?"
assistant: "Let me use @serilog-log-analyzer to trace the auth failures"
<commentary>
Finds HTTP 401 responses, correlates with request URLs and JWT auth.
</commentary>
</example>
- <example>
Context: Performance
user: "The app seems slow today"
assistant: "I'll use @serilog-log-analyzer to find slow operations"
<commentary>
Analyzes API-Perf entries, WebSocket timeouts, DB operation durations.
</commentary>
</example>
Delegations:
- <delegation>
Trigger: Database errors found
Target: cryptorobot-ef-expert
Handoff: "Log analysis found DB issues. Investigate: [entity/query details]"
</delegation>
- <delegation>
Trigger: Frontend rendering issues found
Target: cryptorobot-blazor-architect
Handoff: "Log analysis found FE issues. Investigate: [component details]"
</delegation>
---
# Serilog JSON Log Analyzer
You are an expert log analyst for the OTCryptoRobot trading system. Analyze Serilog JSON logs and provide actionable insights.
## Input Format
Logs are in CLEF (Compact Log Event Format) written by `Serilog.Formatting.Compact.CompactJsonFormatter`:
```json
{"@t":"2026-02-16T19:05:52.787+04:00","@l":"Warning","@m":"HTTP 401 from GET https://api.coinbase.com/...","@x":"Exception...","StatusCode":401,"Url":"..."}
```
**Field mapping:**
- `@t` = timestamp (ISO 8601)
- `@l` = log level (Verbose/Debug/Information/Warning/Error/Fatal). If missing, level is Information.
- `@m` = rendered message
- `@mt` = message template (with placeholders like `{Url}`)
- `@x` = exception (full stack trace)
- `@i` = event id (hex)
- `@r` = renderings
- All other properties are structured data (e.g., `StatusCode`, `MarketCode`, `ThreadId`, `SourceContext`)
## Analysis Steps
### 1. Overview
- Total log count
- Time range (first to last entry)
- Log level distribution (count + percentage for each level)
- Events per minute/hour throughput
### 2. Errors & Exceptions (PRIORITY)
- Group all Error/Fatal entries by exception type or message pattern
- For each group: count, first/last occurrence, sample message with timestamp
- Identify error patterns (do errors cluster in time? repeat periodically?)
- Trace root cause chains (which Warning leads to which Error?)
- Flag Fatal level events immediately
### 3. Performance Analysis
- Find slow operations (look for properties: elapsed, duration, ExecMs, WaitMs)
- Identify `[API-Perf]` entries and their timings
- Find timeout patterns (TimeoutError, RequestTimeout)
- Database operation timings if present
### 4. External Services (Coinbase API)
- HTTP response failures (401, 429, 500) — group by status code and URL
- WebSocket disconnections/reconnections
- Authentication failures (`authentication failure`, `Unauthorized`)
- Rate limiting events
### 5. Trading Operations
- Order state changes (Open, Filled, Cancelled)
- Subscription failures (Ticker, User)
- Price update gaps (missing data periods)
### 6. Patterns & Anomalies
- Repeating error cycles
- Sudden spikes in error rate
- Gaps in logging (service downtime?)
- Unusual event sequences
### 7. Structured Properties
- Extract and analyze: CorrelationId, OrderId, MarketCode, ThreadId, SourceContext
- Group events by request flow
- Identify which entities/operations cause most errors
## Output Format
### CRITICAL ISSUES (fix immediately)
- [Issue with exact log evidence: timestamp + message]
### WARNINGS (investigate soon)
- [Pattern that may become critical]
### OBSERVATIONS
- [Performance notes, interesting patterns]
### TIMELINE
- Chronological summary of significant events
### RECOMMENDATIONS
- Specific actionable fixes based on log evidence
## Rules
- Always quote specific log entries as evidence (timestamp + message)
- If logs contain sensitive data (API keys, passwords, JWT tokens), flag it as a security issue
- Calculate metrics: error rate %, mean time between failures, avg response times
- For `DbUpdateConcurrencyException` — analyze entity, frequency, suggest specific fix
- For WebSocket errors — analyze reconnection pattern and connection stability
- For HTTP 401 — check if JWT auth is present, correlate with `/time` sync calls
- Focus on actionable insights, not just listing errors
- When reading large logs, use the `.json` file for structured analysis, `.txt` for full context with Verbose entries
+10
View File
@@ -0,0 +1,10 @@
{
"version": 2,
"name": "solid",
"owner": "ramziddin",
"repo": "solid-skills",
"path": "skills/solid",
"branch": "main",
"sha": "3f43af8adc4bddd607640a7571365dca69ec2285",
"source": "manual"
}
+270
View File
@@ -0,0 +1,270 @@
---
name: solid
description: Use this skill when writing code, implementing features, refactoring, planning architecture, designing systems, reviewing code, or debugging. This skill transforms junior-level code into senior-engineer quality software through SOLID principles, TDD, clean code practices, and professional software design.
---
# Solid Skills: Professional Software Engineering
You are now operating as a senior software engineer. Every line of code you write, every design decision you make, and every refactoring you perform must embody professional craftsmanship.
## When This Skill Applies
**ALWAYS use this skill when:**
- Writing ANY code (features, fixes, utilities)
- Refactoring existing code
- Planning or designing architecture
- Reviewing code quality
- Debugging issues
- Creating tests
- Making design decisions
## Core Philosophy
> "Code is to create products for users & customers. Testable, flexible, and maintainable code that serves the needs of the users is GOOD because it can be cost-effectively maintained by developers."
The goal of software: Enable developers to **discover, understand, add, change, remove, test, debug, deploy**, and **monitor** features efficiently.
## The Non-Negotiable Process
### 1. ALWAYS Start with Tests (TDD)
**Red-Green-Refactor is not optional:**
```
1. RED - Write a failing test that describes the behavior
2. GREEN - Write the SIMPLEST code to make it pass
3. REFACTOR - Clean up, remove duplication (Rule of Three)
```
**The Three Laws of TDD:**
1. You cannot write production code unless it makes a failing test pass
2. You cannot write more test code than is sufficient to fail
3. You cannot write more production code than is sufficient to pass
**Design happens during REFACTORING, not during coding.**
See: [references/tdd.md](references/tdd.md)
### 2. Apply SOLID Principles Rigorously
Every class, every module, every function:
| Principle | Question to Ask |
|-----------|-----------------|
| **S**RP - Single Responsibility | "Does this have ONE reason to change?" |
| **O**CP - Open/Closed | "Can I extend without modifying?" |
| **L**SP - Liskov Substitution | "Can subtypes replace base types safely?" |
| **I**SP - Interface Segregation | "Are clients forced to depend on unused methods?" |
| **D**IP - Dependency Inversion | "Do high-level modules depend on abstractions?" |
See: [references/solid-principles.md](references/solid-principles.md)
### 3. Write Clean, Human-Readable Code
**Naming (in order of priority):**
1. **Consistency** - Same concept = same name everywhere
2. **Understandability** - Domain language, not technical jargon
3. **Specificity** - Precise, not vague (avoid `data`, `info`, `manager`)
4. **Brevity** - Short but not cryptic
5. **Searchability** - Unique, greppable names
**Structure:**
- One level of indentation per method
- No `else` keyword when possible (early returns)
- When validating untrusted strings against an object/map, use `Object.hasOwn(...)` (or `Object.prototype.hasOwnProperty.call(...)`) — do not use the `in` operator, which matches prototype keys
- **ALWAYS wrap primitives in domain objects** - IDs, emails, money amounts, etc.
- First-class collections (wrap arrays in classes)
- One dot per line (Law of Demeter)
- Keep entities small (< 50 lines for classes, < 10 for methods)
- No more than two instance variables per class
**Value Objects are MANDATORY for:**
```typescript
// ALWAYS create value objects for:
class UserId { constructor(private readonly value: string) {} }
class Email { constructor(private readonly value: string) { /* validate */ } }
class Money { constructor(private readonly amount: number, private readonly currency: string) {} }
class OrderId { constructor(private readonly value: string) {} }
// NEVER use raw primitives for domain concepts:
// BAD: function createOrder(userId: string, email: string)
// GOOD: function createOrder(userId: UserId, email: Email)
```
See: [references/clean-code.md](references/clean-code.md)
### 4. Design with Responsibility in Mind
**Ask these questions for every class:**
1. "What pattern is this?" (Entity, Service, Repository, Factory, etc.)
2. "Is it doing too much?" (Check object calisthenics)
**Object Stereotypes:**
- **Information Holder** - Holds data, minimal behavior
- **Structurer** - Manages relationships between objects
- **Service Provider** - Performs work, stateless operations
- **Coordinator** - Orchestrates multiple services
- **Controller** - Makes decisions, delegates work
- **Interfacer** - Transforms data between systems
See: [references/object-design.md](references/object-design.md)
### 5. Manage Complexity Ruthlessly
**Essential complexity** = inherent to the problem domain
**Accidental complexity** = introduced by our solutions
**Detect complexity through:**
- Change amplification (small change = many files)
- Cognitive load (hard to understand)
- Unknown unknowns (surprises in behavior)
**Fight complexity with:**
- YAGNI - Don't build what you don't need NOW
- KISS - Simplest solution that works
- DRY - But only after Rule of Three (wait for 3 duplications)
See: [references/complexity.md](references/complexity.md)
### 6. Architect for Change
**Vertical Slicing:**
- Features as end-to-end slices
- Each feature self-contained
**Horizontal Decoupling:**
- Layers don't know about each other's internals
- Dependencies point inward (toward domain)
**The Dependency Rule:**
- Source code dependencies point toward high-level policies
- Infrastructure depends on domain, never reverse
See: [references/architecture.md](references/architecture.md)
## The Four Elements of Simple Design (XP)
In priority order:
1. **Runs all the tests** - Must work correctly
2. **Expresses intent** - Readable, reveals purpose
3. **No duplication** - DRY (but Rule of Three)
4. **Minimal** - Fewest classes, methods possible
## Code Smell Detection
**Stop and refactor when you see:**
| Smell | Solution |
|-------|----------|
| Long Method | Extract methods, compose method pattern |
| Large Class | Extract class, single responsibility |
| Long Parameter List | Introduce parameter object |
| Divergent Change | Split into focused classes |
| Shotgun Surgery | Move related code together |
| Feature Envy | Move method to the envied class |
| Data Clumps | Extract class for grouped data |
| Primitive Obsession | Wrap in value objects |
| Switch Statements | Replace with polymorphism |
| Parallel Inheritance | Merge hierarchies |
| Speculative Generality | YAGNI - remove unused abstractions |
See: [references/code-smells.md](references/code-smells.md)
## Design Patterns Awareness
**Creational:** Singleton, Factory, Builder, Prototype
**Structural:** Adapter, Bridge, Decorator, Composite, Proxy
**Behavioral:** Strategy, Observer, Template Method, Command
**Warning:** Don't force patterns. Let them emerge from refactoring.
See: [references/design-patterns.md](references/design-patterns.md)
## Testing Strategy
**Test Types (from inner to outer):**
1. **Unit Tests** - Single class/function, fast, isolated
2. **Integration Tests** - Multiple components together
3. **E2E/Acceptance Tests** - Full system, user perspective
**Arrange-Act-Assert Pattern:**
```typescript
// Arrange - Set up test state
const calculator = new Calculator();
// Act - Execute the behavior
const result = calculator.add(2, 3);
// Assert - Verify the outcome
expect(result).toBe(5);
```
**Test Naming:** Use concrete examples, not abstract statements
```typescript
// BAD: 'can add numbers'
// GOOD: 'when adding 2 + 3, returns 5'
```
See: [references/testing.md](references/testing.md)
## Behavioral Principles
- **Tell, Don't Ask** - Command objects, don't query and decide
- **Design by Contract** - Preconditions, postconditions, invariants
- **Hollywood Principle** - "Don't call us, we'll call you" (IoC)
- **Law of Demeter** - Only talk to immediate friends
## Pre-Code Checklist
Before writing ANY code, answer:
1. [ ] Do I understand the requirement? (Write acceptance criteria first)
2. [ ] What test will I write first?
3. [ ] What is the simplest solution?
4. [ ] What patterns might apply? (Don't force them)
5. [ ] Am I solving a real problem or a hypothetical one?
## During-Code Checklist
While coding, continuously ask:
1. [ ] Is this the simplest thing that could work?
2. [ ] Does this class have a single responsibility?
3. [ ] Am I depending on abstractions or concretions?
4. [ ] Can I name this more clearly?
5. [ ] Is there duplication I should extract? (Rule of Three)
## Post-Code Checklist
After the code works:
1. [ ] Do all tests pass?
2. [ ] Is there any dead code to remove?
3. [ ] Can I simplify any complex conditions?
4. [ ] Are names still accurate after changes?
5. [ ] Would a junior understand this in 6 months?
## Red Flags - Stop and Rethink
- Writing code without a test
- Class with more than 2 instance variables
- Method longer than 10 lines
- More than one level of indentation
- Using `else` when early return works
- Hardcoding values that should be configurable
- Creating abstractions before the third duplication
- Adding features "just in case"
- Depending on concrete implementations
- God classes that know everything
## Remember
> "A little bit of duplication is 10x better than the wrong abstraction."
> "Focus on WHAT needs to happen, not HOW it needs to happen."
> "Design principles become second nature through practice. Eventually, you won't think about SOLID - you'll just write SOLID code."
The journey: Code-first → Best-practice-first → Pattern-first → Responsibility-first → **Systems Thinking**
Your goal is to reach systems thinking - where principles are internalized and you focus on optimizing the entire development process.
+318
View File
@@ -0,0 +1,318 @@
# Software Architecture
## The Goal of Architecture
Enable the development team to:
1. **Add** features with minimal friction
2. **Change** existing features safely
3. **Remove** features cleanly
4. **Test** features in isolation
5. **Deploy** independently when possible
## Architectural Principles
### 1. Vertical Boundaries (Features/Slices)
Organize by **feature**, not by technical layer.
```
BAD: Layer-first
src/
controllers/
UserController.ts
OrderController.ts
services/
UserService.ts
OrderService.ts
repositories/
UserRepository.ts
OrderRepository.ts
GOOD: Feature-first
src/
users/
UserController.ts
UserService.ts
UserRepository.ts
orders/
OrderController.ts
OrderService.ts
OrderRepository.ts
```
**Why:** Changes to "users" feature stay in `users/`. High cohesion within features.
### 2. Horizontal Boundaries (Layers)
Separate concerns into layers with clear dependencies.
```
┌──────────────────────────────────────┐
│ Presentation │ UI, Controllers, CLI
├──────────────────────────────────────┤
│ Application │ Use Cases, Orchestration
├──────────────────────────────────────┤
│ Domain │ Business Logic, Entities
├──────────────────────────────────────┤
│ Infrastructure │ Database, APIs, External
└──────────────────────────────────────┘
```
### 3. The Dependency Rule
**Dependencies point INWARD.**
```
Infrastructure → Application → Domain
↓ ↓ ↓
(outer) (middle) (inner)
```
- Inner layers know NOTHING about outer layers
- Domain has zero dependencies on infrastructure
- Use interfaces to invert dependencies
```typescript
// Domain defines the interface (inner)
interface UserRepository {
save(user: User): Promise<void>;
findById(id: UserId): Promise<User | null>;
}
// Infrastructure implements it (outer)
class PostgresUserRepository implements UserRepository {
save(user: User): Promise<void> {
// SQL here
}
}
// Domain service uses the interface
class UserService {
constructor(private repo: UserRepository) {} // Depends on abstraction
}
```
### 4. Contracts
Interfaces define boundaries between components.
```typescript
// The contract
interface PaymentGateway {
charge(amount: Money, card: CardDetails): Promise<ChargeResult>;
refund(chargeId: string): Promise<RefundResult>;
}
// Multiple implementations possible
class StripeGateway implements PaymentGateway { }
class PayPalGateway implements PaymentGateway { }
class MockGateway implements PaymentGateway { } // For tests
```
### 5. Cross-Cutting Concerns
Concerns that span multiple features: logging, auth, validation, error handling.
**Options:**
- Middleware/interceptors
- Decorators
- Aspect-oriented approaches
- Base classes (use sparingly)
```typescript
// Middleware approach
class LoggingMiddleware {
handle(request: Request, next: Handler): Response {
console.log(`Request: ${request.path}`);
const response = next(request);
console.log(`Response: ${response.status}`);
return response;
}
}
```
### 6. Conway's Law
> "Organizations design systems that mirror their communication structure."
**Implication:** Team structure affects architecture. Align both intentionally.
---
## Common Architectural Styles
### Layered Architecture
Traditional layers: Presentation → Business → Persistence
**Pros:** Simple, well-understood
**Cons:** Can become a "big ball of mud" without discipline
### Hexagonal Architecture (Ports & Adapters)
Domain at center, adapters around the edges.
```
┌─────────────────────┐
│ HTTP Adapter │
└─────────┬───────────┘
┌─────────────────▼─────────────────┐
│ DOMAIN │
│ ┌─────────────────────────┐ │
│ │ Business Logic │ │
│ │ Use Cases │ │
│ └─────────────────────────┘ │
└─────────────────┬─────────────────┘
┌─────────▼───────────┐
│ Database Adapter │
└─────────────────────┘
```
**Ports:** Interfaces defined by the domain
**Adapters:** Implementations that connect to the outside world
### Clean Architecture
Similar to Hexagonal, with explicit layers:
1. **Entities** - Enterprise business rules
2. **Use Cases** - Application business rules
3. **Interface Adapters** - Controllers, Presenters, Gateways
4. **Frameworks & Drivers** - Web, DB, External interfaces
---
## Feature-Driven Structure (Frontend)
```
src/
features/
auth/
components/
LoginForm.tsx
SignupForm.tsx
hooks/
useAuth.ts
services/
authService.ts
types/
auth.types.ts
index.ts # Public API
checkout/
components/
hooks/
services/
types/
index.ts
shared/
components/ # Truly shared UI
hooks/ # Truly shared hooks
utils/ # Truly shared utilities
```
---
## Feature-Driven Structure (Backend)
```
src/
modules/
users/
domain/
User.ts
UserRepository.ts # Interface
application/
CreateUser.ts # Use case
GetUser.ts # Use case
infrastructure/
PostgresUserRepo.ts
presentation/
UserController.ts
UserDTO.ts
orders/
domain/
application/
infrastructure/
presentation/
shared/
domain/ # Shared value objects
infrastructure/ # Shared infra utilities
```
---
## The Walking Skeleton
Start with a minimal end-to-end slice:
1. **Thinnest possible feature** that touches all layers
2. **Deployable** from day one
3. **Proves the architecture** works
Example walking skeleton for e-commerce:
- User can view ONE product (hardcoded)
- User can add it to cart
- User can "checkout" (just logs)
From there, flesh out each feature fully.
---
## Testing Architecture
```
┌────────────────────────────────────────────┐
│ E2E / Acceptance Tests │ Few, slow, high confidence
├────────────────────────────────────────────┤
│ Integration Tests │ Some, medium speed
├────────────────────────────────────────────┤
│ Unit Tests │ Many, fast, isolated
└────────────────────────────────────────────┘
```
**Test by layer:**
- **Domain:** Unit tests (most tests here)
- **Application:** Integration tests with mocked infra
- **Infrastructure:** Integration tests with real dependencies
- **E2E:** Critical paths only
---
## Architecture Decision Records (ADRs)
Document significant decisions:
```markdown
# ADR 001: Use PostgreSQL for persistence
## Status
Accepted
## Context
We need a database. Options: PostgreSQL, MongoDB, MySQL
## Decision
PostgreSQL for:
- ACID compliance
- Team familiarity
- JSON support for flexibility
## Consequences
- Need PostgreSQL expertise
- Schema migrations required
- Excellent query capabilities
```
---
## Red Flags in Architecture
- **Circular dependencies** between modules
- **Domain depending on infrastructure**
- **Framework code in business logic**
- **No clear boundaries** between features
- **Shared mutable state** across modules
- **"Util" or "Common" packages** that grow forever
- **Database schema driving domain model**
+376
View File
@@ -0,0 +1,376 @@
# Clean Code Practices
## What is Clean Code?
Code that is:
- **Easy to understand** - reveals intent clearly
- **Easy to change** - modifications are localized
- **Easy to test** - dependencies are injectable
- **Simple** - no unnecessary complexity
## The Human-Centered Approach
Code has THREE consumers:
1. **Users** - get their needs met
2. **Customers** - make or save money
3. **Developers** - must maintain it
Design for all three, but remember: **developers read code 10x more than they write it.**
## Naming Principles
### 1. Consistency & Uniqueness (HIGHEST PRIORITY)
Same concept = same name everywhere. One name per concept.
```typescript
// BAD: Inconsistent names for same concept
getUserById(id)
fetchCustomerById(id)
retrieveClientById(id)
// GOOD: Consistent
getUser(id)
getOrder(id)
getProduct(id)
```
### 2. Understandability
Use domain language, not technical jargon.
```typescript
// BAD: Technical
const arr = users.filter(u => u.isActive);
// GOOD: Domain language
const activeCustomers = users.filter(user => user.isActive);
```
### 3. Specificity
Avoid vague names: `data`, `info`, `manager`, `handler`, `processor`, `utils`
```typescript
// BAD: Vague
class DataManager { }
function processInfo(data) { }
// GOOD: Specific
class OrderRepository { }
function validatePayment(payment) { }
```
### 4. Brevity (but not at cost of clarity)
Short names are good only if meaning is preserved.
```typescript
// BAD: Too cryptic
const usrLst = getUsrs();
// BAD: Unnecessarily long
const listOfAllActiveUsersInTheSystem = getActiveUsers();
// GOOD: Brief but clear
const activeUsers = getActiveUsers();
```
### 5. Searchability
Names should be unique enough to grep/search.
```typescript
// BAD: Common word, hard to search
const data = fetch();
// GOOD: Unique, searchable
const orderSummary = fetchOrderSummary();
```
### 6. Pronounceability
You should be able to say it in conversation.
```typescript
// BAD
const genymdhms = generateYearMonthDayHourMinuteSecond();
// GOOD
const timestamp = generateTimestamp();
```
### 7. Austerity
Avoid unnecessary filler words.
```typescript
// BAD: Redundant
const userData = user; // 'Data' adds nothing
class UserClass { } // 'Class' adds nothing
// GOOD
const user = user;
class User { }
```
---
## Object Calisthenics (9 Rules)
Exercises to improve OO design. Follow strictly during practice, relax slightly in production.
### 1. One Level of Indentation per Method
```typescript
// BAD: Multiple levels
function process(orders: Order[]) {
for (const order of orders) {
if (order.isValid()) {
for (const item of order.items) {
if (item.inStock) {
// process...
}
}
}
}
}
// GOOD: Extract methods
function process(orders: Order[]) {
orders.filter(o => o.isValid()).forEach(processOrder);
}
function processOrder(order: Order) {
order.items.filter(i => i.inStock).forEach(processItem);
}
```
### 2. Don't Use the ELSE Keyword
Use early returns, guard clauses, or polymorphism.
```typescript
// BAD: else
function getDiscount(user: User): number {
if (user.isPremium) {
return 20;
} else {
return 0;
}
}
// GOOD: Early return
function getDiscount(user: User): number {
if (user.isPremium) return 20;
return 0;
}
```
### 3. Wrap All Primitives and Strings
Primitives should be wrapped in domain objects when they have meaning.
```typescript
// BAD: Primitive obsession
function createUser(email: string, age: number) { }
// GOOD: Value objects
class Email {
constructor(private value: string) {
if (!this.isValid(value)) throw new InvalidEmail();
}
private isValid(email: string): boolean { ... }
}
class Age {
constructor(private value: number) {
if (value < 0 || value > 150) throw new InvalidAge();
}
}
function createUser(email: Email, age: Age) { }
```
### 4. First-Class Collections
Any class with a collection should have no other instance variables.
```typescript
// BAD: Collection mixed with other state
class Order {
items: OrderItem[] = [];
customerId: string;
total: number;
}
// GOOD: Collection is its own class
class OrderItems {
constructor(private items: OrderItem[] = []) {}
add(item: OrderItem): void { ... }
total(): Money { ... }
isEmpty(): boolean { ... }
}
class Order {
constructor(
private items: OrderItems,
private customerId: CustomerId
) {}
}
```
### 5. One Dot per Line (Law of Demeter)
Don't chain through object graphs.
```typescript
// BAD: Train wreck
const city = order.customer.address.city;
// GOOD: Tell, don't ask
const city = order.getShippingCity();
```
### 6. Don't Abbreviate
If a name is too long to type, the class is doing too much.
```typescript
// BAD
const custRepo = new CustRepo();
const ord = new Ord();
// GOOD
const customerRepository = new CustomerRepository();
const order = new Order();
```
### 7. Keep All Entities Small
- Classes: < 50 lines
- Methods: < 10 lines
- Files: < 100 lines
If larger, it's probably doing too much. Split it.
### 8. No Classes with More Than Two Instance Variables
Forces small, focused classes.
```typescript
// BAD: Too many variables
class Order {
id: string;
customerId: string;
items: Item[];
total: number;
status: string;
}
// GOOD: Composed of smaller objects
class Order {
constructor(
private id: OrderId,
private details: OrderDetails
) {}
}
class OrderDetails {
constructor(
private customer: Customer,
private lineItems: LineItems
) {}
}
```
### 9. No Getters/Setters/Properties
Objects should have behavior, not just data. Tell objects what to do.
```typescript
// BAD: Data bag with getters
class Account {
getBalance(): number { return this.balance; }
setBalance(value: number) { this.balance = value; }
}
// Caller does the work
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
}
// GOOD: Behavior-rich object
class Account {
withdraw(amount: Money): WithdrawResult {
if (!this.canWithdraw(amount)) {
return WithdrawResult.insufficientFunds();
}
this.balance = this.balance.subtract(amount);
return WithdrawResult.success();
}
}
// Caller tells, object decides
const result = account.withdraw(amount);
```
---
## Comments
### When to Write Comments
**Only write comments to explain WHY, not WHAT or HOW.**
Code explains what and how. Comments explain business reasons, non-obvious decisions, or warnings.
```typescript
// BAD: Explains what (redundant)
// Add 1 to counter
counter++;
// GOOD: Explains why
// Compensate for 0-based indexing in legacy API
counter++;
```
### Prefer Self-Documenting Code
Instead of commenting, rename to make intent clear.
```typescript
// BAD: Comment needed
// Check if user can access premium features
if (user.subscriptionLevel >= 2 && !user.isBanned) { }
// GOOD: Self-documenting
if (user.canAccessPremiumFeatures()) { }
```
---
## Formatting
### Vertical Spacing
- Related code together
- Blank lines between concepts
- Most important/public at top
### Horizontal Spacing
- Consistent indentation
- Space around operators
- Max line length ~80-120 characters
### Storytelling
Code should read top-to-bottom like a story. High-level at top, details below.
```typescript
class OrderProcessor {
// Public API first
process(order: Order): ProcessResult {
this.validate(order);
this.calculateTotals(order);
return this.save(order);
}
// Supporting methods below, in order of appearance
private validate(order: Order): void { ... }
private calculateTotals(order: Order): void { ... }
private save(order: Order): ProcessResult { ... }
}
```
+334
View File
@@ -0,0 +1,334 @@
# Code Smells & Anti-Patterns
## What Are Code Smells?
Indicators that something MAY be wrong. Not bugs, but design problems that make code hard to understand, change, or test.
## The Five Categories
### 1. Bloaters
Code that has grown too large.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Long Method** | > 10 lines | Extract Method |
| **Large Class** | > 50 lines, multiple responsibilities | Extract Class |
| **Long Parameter List** | > 3 parameters | Introduce Parameter Object |
| **Data Clumps** | Same group of variables appear together | Extract Class |
| **Primitive Obsession** | Primitives instead of small objects | Wrap in Value Object |
### 2. Object-Orientation Abusers
Misuse of OO principles.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Switch Statements** | Type checking, large switch/if-else | Replace with Polymorphism |
| **Parallel Inheritance** | Adding subclass requires adding another | Merge Hierarchies |
| **Refused Bequest** | Subclass doesn't use parent methods | Replace Inheritance with Delegation |
| **Alternative Classes** | Different interfaces, same concept | Rename, Extract Superclass |
### 3. Change Preventers
Code that makes changes difficult.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Divergent Change** | One class changed for many reasons | Extract Class (SRP) |
| **Shotgun Surgery** | One change touches many classes | Move Method/Field together |
| **Parallel Inheritance** | (see above) | Merge Hierarchies |
### 4. Dispensables
Code that can be removed.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Comments** | Explaining bad code | Rename, Extract Method |
| **Duplicate Code** | Copy-paste | Extract Method, Pull Up Method |
| **Dead Code** | Unreachable code | Delete |
| **Speculative Generality** | "Just in case" code | Delete (YAGNI) |
| **Lazy Class** | Class that does almost nothing | Inline Class |
### 5. Couplers
Excessive coupling between classes.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Feature Envy** | Method uses another class's data extensively | Move Method |
| **Inappropriate Intimacy** | Classes know too much about each other | Move Method, Extract Class |
| **Message Chains** | `a.getB().getC().getD()` | Hide Delegate |
| **Middle Man** | Class only delegates | Inline Class |
---
## The Seven Most Common Code Smells
### 1. Long Method
**Symptom:** Method > 10 lines, doing multiple things.
```typescript
// SMELL
function processOrder(order: Order) {
// Validate
if (!order.items.length) throw new Error('Empty');
if (!order.customer) throw new Error('No customer');
// Calculate
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
if (item.discount) {
total -= item.discount;
}
}
// Apply tax
const taxRate = getTaxRate(order.customer.state);
total = total * (1 + taxRate);
// Save
db.orders.insert({ ...order, total });
// Notify
emailService.send(order.customer.email, 'Order confirmed');
}
// REFACTORED
function processOrder(order: Order) {
validateOrder(order);
const total = calculateTotal(order);
saveOrder(order, total);
notifyCustomer(order);
}
```
### 2. Large Class
**Symptom:** Class with many responsibilities, > 50 lines.
```typescript
// SMELL: God class
class User {
// User data
name: string;
email: string;
// Authentication
login() { }
logout() { }
resetPassword() { }
// Preferences
setTheme() { }
setLanguage() { }
// Notifications
sendEmail() { }
sendSMS() { }
// Billing
charge() { }
refund() { }
}
// REFACTORED: Separate classes
class User { name: string; email: string; }
class AuthService { login(); logout(); resetPassword(); }
class UserPreferences { setTheme(); setLanguage(); }
class NotificationService { sendEmail(); sendSMS(); }
class BillingService { charge(); refund(); }
```
### 3. Feature Envy
**Symptom:** Method uses another class's data more than its own.
```typescript
// SMELL: Order envies Customer
class Order {
calculateShipping(customer: Customer): number {
if (customer.country === 'US') {
if (customer.state === 'CA') return 10;
return 15;
}
return 25;
}
}
// REFACTORED: Move to Customer
class Customer {
getShippingCost(): number {
if (this.country === 'US') {
if (this.state === 'CA') return 10;
return 15;
}
return 25;
}
}
class Order {
calculateShipping(): number {
return this.customer.getShippingCost();
}
}
```
### 4. Primitive Obsession
**Symptom:** Using primitives for domain concepts.
```typescript
// SMELL
function createUser(email: string, age: number, zipCode: string) {
// No validation, easy to pass wrong values
if (!email.includes('@')) throw new Error();
if (age < 0) throw new Error();
}
// REFACTORED: Value objects
class Email {
constructor(private value: string) {
if (!value.includes('@')) throw new InvalidEmail();
}
}
class Age {
constructor(private value: number) {
if (value < 0 || value > 150) throw new InvalidAge();
}
}
function createUser(email: Email, age: Age, address: Address) {
// Type system prevents invalid data
}
```
### 5. Switch Statements
**Symptom:** Switching on type, repeated across codebase.
```typescript
// SMELL
function getArea(shape: Shape): number {
switch (shape.type) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return 0.5 * shape.base * shape.height;
}
}
function getPerimeter(shape: Shape): number {
switch (shape.type) { // Same switch again!
case 'circle': return 2 * Math.PI * shape.radius;
// ...
}
}
// REFACTORED: Polymorphism
interface Shape {
getArea(): number;
getPerimeter(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea(): number { return Math.PI * this.radius ** 2; }
getPerimeter(): number { return 2 * Math.PI * this.radius; }
}
```
### 6. Inappropriate Intimacy
**Symptom:** Classes know too much about each other's internals.
```typescript
// SMELL
class Order {
process() {
const inventory = new Inventory();
// Reaching into inventory's internals
for (const item of this.items) {
const stock = inventory.stockLevels[item.sku];
if (stock.quantity < item.quantity) {
throw new Error('Out of stock');
}
inventory.stockLevels[item.sku].quantity -= item.quantity;
}
}
}
// REFACTORED: Tell, don't ask
class Inventory {
reserve(items: OrderItem[]): ReserveResult {
// Inventory manages its own state
for (const item of items) {
if (!this.canReserve(item)) {
return ReserveResult.outOfStock(item);
}
}
this.deductStock(items);
return ReserveResult.success();
}
}
class Order {
process(inventory: Inventory) {
const result = inventory.reserve(this.items);
if (!result.isSuccess()) {
throw new OutOfStockError(result.failedItem);
}
}
}
```
### 7. Speculative Generality
**Symptom:** "Just in case" abstractions that aren't used.
```typescript
// SMELL: Over-engineered for hypothetical needs
interface PaymentProcessor {
process(): void;
rollback(): void;
audit(): void;
generateReport(): void;
scheduleRecurring(): void;
}
class StripeProcessor implements PaymentProcessor {
process() { /* actual code */ }
rollback() { throw new Error('Not implemented'); }
audit() { throw new Error('Not implemented'); }
generateReport() { throw new Error('Not implemented'); }
scheduleRecurring() { throw new Error('Not implemented'); }
}
// REFACTORED: YAGNI
interface PaymentProcessor {
process(): void;
}
class StripeProcessor implements PaymentProcessor {
process() { /* actual code */ }
}
// Add other methods when actually needed
```
---
## Prevention Strategies
1. **Follow Object Calisthenics** - Rules prevent most smells
2. **Practice TDD** - Tests reveal design problems early
3. **Review in pairs** - Fresh eyes catch smells
4. **Refactor continuously** - Don't let smells accumulate
5. **Apply SOLID** - Prevents structural smells
6. **Use static analysis** - Tools catch common issues
---
## When You Find a Smell
1. **Confirm it's a problem** - Not all smells need fixing
2. **Ensure test coverage** - Before refactoring
3. **Refactor in small steps** - Keep tests passing
4. **Commit frequently** - Easy to revert if needed
+286
View File
@@ -0,0 +1,286 @@
# Managing Complexity
## The Two Types of Complexity
### Essential Complexity
Inherent to the problem domain. Cannot be removed, only managed.
- Business rules
- Domain logic
- User requirements
### Accidental Complexity
Introduced by our solutions. CAN and SHOULD be minimized.
- Poor abstractions
- Unnecessary indirection
- Framework ceremony
- Technical debt
**Goal: Minimize accidental complexity while clearly expressing essential complexity.**
---
## Detecting Complexity
### 1. Change Amplification
Small changes require touching many files.
**Symptom:** "To add this field, I need to update 15 files."
**Cause:** Scattered responsibilities, poor abstraction boundaries.
### 2. Cognitive Load
Code is hard to understand, requires holding too much in memory.
**Symptom:** "I need to understand 10 other classes to understand this one."
**Cause:** Tight coupling, hidden dependencies, unclear naming.
### 3. Unknown Unknowns
Behavior is surprising, side effects are hidden.
**Symptom:** "I changed this, and something completely unrelated broke."
**Cause:** Global state, hidden dependencies, implicit contracts.
---
## The XP Values for Fighting Complexity
From Extreme Programming:
### 1. Communication
Code should communicate clearly. Names, structure, tests all contribute.
### 2. Simplicity
Do the simplest thing that could possibly work.
### 3. Feedback
Fast feedback loops catch complexity early. TDD, CI, code review.
### 4. Courage
Refactor aggressively. Don't let complexity accumulate.
### 5. Respect
Respect future readers (including yourself). Write for humans first.
---
## KISS - Keep It Simple, Silly
> "The simplest solution that works is usually the best."
### How to Apply:
1. Start with the obvious solution
2. Only add complexity when REQUIRED
3. Prefer boring, well-understood approaches
4. Question every abstraction
```typescript
// Over-engineered
class UserServiceFactoryProvider {
private static instance: UserServiceFactoryProvider;
static getInstance(): UserServiceFactoryProvider { ... }
createFactory(): UserServiceFactory { ... }
}
// KISS
class UserService {
getUser(id: string): User { ... }
}
```
---
## YAGNI - You Aren't Gonna Need It
> "Don't build features until they're actually needed."
### Warning Signs:
- "We might need this later"
- "It would be nice to have"
- "Just in case"
- "For future extensibility"
### The Cost of YAGNI Violations:
1. **Development time** - Building unused features
2. **Maintenance burden** - Code that must be maintained
3. **Cognitive load** - More to understand
4. **Wrong abstraction** - Guessing future needs incorrectly
```typescript
// YAGNI violation: Building for hypothetical needs
class User {
// "We might need these someday"
middleName?: string;
secondaryEmail?: string;
faxNumber?: string;
linkedinProfile?: string;
twitterHandle?: string;
}
// YAGNI: Only what's needed NOW
class User {
name: string;
email: Email;
}
```
---
## DRY - Don't Repeat Yourself (with The Rule of Three)
> "Every piece of knowledge should have a single, unambiguous representation."
### BUT: The Rule of Three
**Don't extract duplication until you see it THREE times.**
Why? The wrong abstraction is worse than duplication.
```
Duplication #1 → Leave it
Duplication #2 → Note it, leave it
Duplication #3 → NOW extract it
```
### Example:
```typescript
// First time - leave it
function processUserOrder(order) {
validate(order);
calculateTax(order);
save(order);
}
// Second time - note the similarity, but leave it
function processGuestOrder(order) {
validate(order);
calculateTax(order);
save(order);
sendGuestEmail(order);
}
// Third time - NOW extract
function processCorporateOrder(order) {
validate(order);
calculateTax(order);
save(order);
applyCorporateDiscount(order);
}
// After three, extract the common parts
function processOrder(order: Order, postProcessing: (o: Order) => void) {
validate(order);
calculateTax(order);
save(order);
postProcessing(order);
}
```
---
## Separation of Concerns
> "Each module should address a single concern."
### Concerns to Separate:
- **Business logic** vs **Infrastructure**
- **What** (policy) vs **How** (mechanism)
- **Input** vs **Processing** vs **Output**
- **Data** vs **Behavior**
### Example:
```typescript
// BAD: Mixed concerns
class OrderProcessor {
process(order: Order) {
// Validation
if (!order.items.length) throw new Error('Empty');
// Business logic
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
// Persistence
const db = new Database();
db.query(`INSERT INTO orders...`);
// Notification
const email = new EmailClient();
email.send(order.customer.email, 'Order confirmed');
}
}
// GOOD: Separated concerns
class OrderProcessor {
constructor(
private validator: OrderValidator,
private calculator: OrderCalculator,
private repository: OrderRepository,
private notifier: OrderNotifier
) {}
process(order: Order): ProcessResult {
this.validator.validate(order);
const total = this.calculator.calculateTotal(order);
const savedOrder = this.repository.save(order);
this.notifier.notifyConfirmation(savedOrder);
return ProcessResult.success(savedOrder);
}
}
```
---
## Managing Technical Debt
### Types of Technical Debt:
1. **Deliberate** - Conscious trade-off for speed
2. **Accidental** - Mistakes, lack of knowledge
3. **Bit rot** - Code degrades over time
### The Boy Scout Rule:
> "Leave the code better than you found it."
Every time you touch code:
- Improve one small thing
- Fix one naming issue
- Extract one method
- Add one missing test
### When to Pay Down Debt:
- When it's in your path (you're already there)
- When it's blocking new features
- When it's causing bugs
- During dedicated refactoring time
### When NOT to Refactor:
- Code that works and won't change
- Code being replaced soon
- When you don't have tests
---
## The Four Elements of Simple Design
In priority order (from XP):
1. **Runs all the tests**
- If it doesn't work, nothing else matters
2. **Expresses intent**
- Clear names, obvious structure
- Code tells the story
3. **No duplication**
- DRY (but Rule of Three)
- Single source of truth
4. **Minimal**
- Fewest classes and methods possible
- Remove anything unnecessary
If these four are true, the design is simple enough.
+504
View File
@@ -0,0 +1,504 @@
# Design Patterns
## What Are Design Patterns?
Reusable solutions to common design problems. A shared vocabulary for discussing design.
## WARNING: Don't Force Patterns
> "Let patterns emerge from refactoring, don't force them upfront."
Patterns should solve problems you HAVE, not problems you MIGHT have.
## When to Use Patterns
1. **You recognize the problem** - You've seen it before
2. **The pattern fits** - Not forcing it
3. **It simplifies** - Doesn't add unnecessary complexity
4. **Team understands it** - Shared knowledge
---
## Creational Patterns
### Singleton
**Purpose:** Ensure only one instance exists.
**When to use:** Global configuration, connection pools, logging.
**Warning:** Often overused. Consider dependency injection instead.
```typescript
class Logger {
private static instance: Logger;
private constructor() {}
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
log(message: string): void { ... }
}
```
### Factory
**Purpose:** Create objects without specifying exact class.
**When to use:** Object creation logic is complex, or varies by type.
```typescript
interface Notification {
send(message: string): void;
}
class EmailNotification implements Notification { ... }
class SMSNotification implements Notification { ... }
class PushNotification implements Notification { ... }
class NotificationFactory {
create(type: 'email' | 'sms' | 'push'): Notification {
switch (type) {
case 'email': return new EmailNotification();
case 'sms': return new SMSNotification();
case 'push': return new PushNotification();
}
}
}
```
### Builder
**Purpose:** Construct complex objects step by step.
**When to use:** Objects with many optional parameters, test data creation.
```typescript
class UserBuilder {
private user: Partial<User> = {};
withName(name: string): UserBuilder {
this.user.name = name;
return this;
}
withEmail(email: string): UserBuilder {
this.user.email = email;
return this;
}
withAge(age: number): UserBuilder {
this.user.age = age;
return this;
}
build(): User {
return new User(
this.user.name!,
this.user.email!,
this.user.age
);
}
}
// Usage
const user = new UserBuilder()
.withName('Alice')
.withEmail('alice@example.com')
.build();
```
### Prototype
**Purpose:** Create new objects by cloning existing ones.
**When to use:** Object creation is expensive, or you need copies with slight variations.
```typescript
interface Prototype {
clone(): Prototype;
}
class Document implements Prototype {
constructor(
public title: string,
public content: string,
public metadata: Metadata
) {}
clone(): Document {
return new Document(
this.title,
this.content,
{ ...this.metadata }
);
}
}
```
---
## Structural Patterns
### Adapter
**Purpose:** Make incompatible interfaces work together.
**When to use:** Integrating third-party libraries, legacy code.
```typescript
// Third-party library with different interface
class OldPaymentAPI {
makePayment(cents: number): boolean { ... }
}
// Our interface
interface PaymentGateway {
charge(amount: Money): ChargeResult;
}
// Adapter
class OldPaymentAdapter implements PaymentGateway {
constructor(private oldAPI: OldPaymentAPI) {}
charge(amount: Money): ChargeResult {
const cents = amount.toCents();
const success = this.oldAPI.makePayment(cents);
return success ? ChargeResult.success() : ChargeResult.failed();
}
}
```
### Decorator
**Purpose:** Add behavior to objects dynamically.
**When to use:** Adding features without modifying existing code.
```typescript
interface Notifier {
send(message: string): void;
}
class EmailNotifier implements Notifier {
send(message: string): void {
console.log(`Email: ${message}`);
}
}
// Decorators
class SMSDecorator implements Notifier {
constructor(private wrapped: Notifier) {}
send(message: string): void {
this.wrapped.send(message);
console.log(`SMS: ${message}`);
}
}
class SlackDecorator implements Notifier {
constructor(private wrapped: Notifier) {}
send(message: string): void {
this.wrapped.send(message);
console.log(`Slack: ${message}`);
}
}
// Usage - compose behaviors
const notifier = new SlackDecorator(
new SMSDecorator(
new EmailNotifier()
)
);
notifier.send('Alert!'); // Sends to all three
```
### Proxy
**Purpose:** Control access to an object.
**When to use:** Lazy loading, access control, logging, caching.
```typescript
interface Image {
display(): void;
}
class RealImage implements Image {
constructor(private filename: string) {
this.loadFromDisk(); // Expensive
}
private loadFromDisk(): void { ... }
display(): void { ... }
}
// Lazy loading proxy
class ImageProxy implements Image {
private realImage: RealImage | null = null;
constructor(private filename: string) {}
display(): void {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
```
### Composite
**Purpose:** Treat individual objects and compositions uniformly.
**When to use:** Tree structures, hierarchies (files/folders, UI components).
```typescript
interface Component {
getPrice(): number;
}
class Product implements Component {
constructor(private price: number) {}
getPrice(): number {
return this.price;
}
}
class Box implements Component {
private children: Component[] = [];
add(component: Component): void {
this.children.push(component);
}
getPrice(): number {
return this.children.reduce(
(sum, child) => sum + child.getPrice(),
0
);
}
}
// Usage
const smallBox = new Box();
smallBox.add(new Product(10));
smallBox.add(new Product(20));
const bigBox = new Box();
bigBox.add(smallBox);
bigBox.add(new Product(50));
console.log(bigBox.getPrice()); // 80
```
---
## Behavioral Patterns
### Strategy
**Purpose:** Define a family of algorithms, make them interchangeable.
**When to use:** Multiple ways to do something, switchable at runtime.
```typescript
interface PricingStrategy {
calculate(basePrice: number): number;
}
class RegularPricing implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice;
}
}
class PremiumDiscount implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice * 0.8; // 20% off
}
}
class BlackFriday implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice * 0.5; // 50% off
}
}
class ShoppingCart {
constructor(private pricing: PricingStrategy) {}
calculateTotal(items: Item[]): number {
const base = items.reduce((sum, i) => sum + i.price, 0);
return this.pricing.calculate(base);
}
}
```
### Observer
**Purpose:** Notify multiple objects about state changes.
**When to use:** Event systems, pub/sub, reactive updates.
```typescript
interface Observer {
update(event: Event): void;
}
class EventEmitter {
private observers: Observer[] = [];
subscribe(observer: Observer): void {
this.observers.push(observer);
}
unsubscribe(observer: Observer): void {
this.observers = this.observers.filter(o => o !== observer);
}
notify(event: Event): void {
this.observers.forEach(o => o.update(event));
}
}
// Usage
class OrderService extends EventEmitter {
placeOrder(order: Order): void {
// Process order...
this.notify({ type: 'ORDER_PLACED', order });
}
}
class EmailService implements Observer {
update(event: Event): void {
if (event.type === 'ORDER_PLACED') {
this.sendConfirmation(event.order);
}
}
}
```
### Template Method
**Purpose:** Define algorithm skeleton, let subclasses override steps.
**When to use:** Common algorithm with varying steps.
```typescript
abstract class DataExporter {
// Template method - defines the algorithm
export(data: Data[]): void {
this.validate(data);
const formatted = this.format(data);
this.write(formatted);
this.notify();
}
// Common steps
private validate(data: Data[]): void { ... }
private notify(): void { ... }
// Steps to override
protected abstract format(data: Data[]): string;
protected abstract write(content: string): void;
}
class CSVExporter extends DataExporter {
protected format(data: Data[]): string {
return data.map(d => d.toCSV()).join('\n');
}
protected write(content: string): void {
fs.writeFileSync('export.csv', content);
}
}
class JSONExporter extends DataExporter {
protected format(data: Data[]): string {
return JSON.stringify(data);
}
protected write(content: string): void {
fs.writeFileSync('export.json', content);
}
}
```
### Command
**Purpose:** Encapsulate a request as an object.
**When to use:** Undo/redo, queuing, logging actions.
```typescript
interface Command {
execute(): void;
undo(): void;
}
class AddItemCommand implements Command {
constructor(
private cart: Cart,
private item: Item
) {}
execute(): void {
this.cart.add(this.item);
}
undo(): void {
this.cart.remove(this.item);
}
}
class CommandHistory {
private history: Command[] = [];
execute(command: Command): void {
command.execute();
this.history.push(command);
}
undo(): void {
const command = this.history.pop();
command?.undo();
}
}
```
---
## Pattern Awareness
### The Four-Dimensional Lens
When analyzing new code/libraries, ask:
1. **What problem does it solve?** (Creational, Structural, Behavioral)
2. **What scope?** (Object-level, Class-level, System-level)
3. **When is it applied?** (Compile-time, Runtime)
4. **How coupled?** (Tight, Loose)
This helps recognize patterns even in unfamiliar code.
---
## Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|--------------|---------|----------|
| **God Object** | Class does everything | Split by responsibility |
| **Spaghetti Code** | Tangled, no structure | Refactor to layers |
| **Golden Hammer** | Using one pattern for everything | Match pattern to problem |
| **Premature Optimization** | Optimizing before needed | YAGNI, profile first |
| **Copy-Paste Programming** | Duplication | Extract, Rule of Three |
+328
View File
@@ -0,0 +1,328 @@
# Object-Oriented Design
## Responsibility-Driven Design (RDD)
The key insight: **Objects are defined by their responsibilities, not their data.**
### Finding Objects
Start with:
1. **Nouns** in requirements → candidate objects
2. **Verbs** → candidate methods/behaviors
3. **Domain concepts** → value objects
### Finding Responsibilities
Each object should answer:
- What does this object **know**?
- What does this object **do**?
- What does this object **decide**?
### Object Stereotypes
Every class fits one (or maybe two) stereotypes:
| Stereotype | Purpose | Example |
|------------|---------|---------|
| **Information Holder** | Knows things, holds data | `User`, `Product`, `Address` |
| **Structurer** | Maintains relationships | `OrderItems`, `UserGroup` |
| **Service Provider** | Performs work | `PaymentProcessor`, `EmailSender` |
| **Coordinator** | Orchestrates workflow | `OrderFulfillmentService` |
| **Controller** | Makes decisions, delegates | `CheckoutController` |
| **Interfacer** | Transforms between systems | `UserAPIAdapter`, `DatabaseMapper` |
### The Two Questions
For every class, ask:
1. **"What pattern is this?"** - Which stereotype? Which design pattern?
2. **"Is it doing too much?"** - Check object calisthenics rules
If you can't answer clearly, the class needs refactoring.
---
## Tell, Don't Ask
**Command objects to do work. Don't interrogate them and do the work yourself.**
```typescript
// BAD: Asking, then doing
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
// more logic here...
}
// GOOD: Telling
const result = account.withdraw(amount);
if (result.isSuccess()) {
// ...
}
```
The object that has the data should have the behavior.
---
## Design by Contract (DbC)
Every method has:
- **Preconditions** - What must be true BEFORE calling
- **Postconditions** - What will be true AFTER calling
- **Invariants** - What is ALWAYS true about the object
```typescript
class BankAccount {
private balance: Money;
// INVARIANT: balance is never negative
// PRECONDITION: amount > 0
// POSTCONDITION: balance decreased by amount OR error returned
withdraw(amount: Money): WithdrawResult {
if (amount.isNegativeOrZero()) {
return WithdrawResult.invalidAmount();
}
if (this.balance.isLessThan(amount)) {
return WithdrawResult.insufficientFunds();
}
this.balance = this.balance.minus(amount);
return WithdrawResult.success(this.balance);
}
}
```
---
## Composition Over Inheritance
**Prefer composing objects over extending classes.**
### Why Inheritance is Problematic:
- Tight coupling between parent and child
- Fragile base class problem
- Difficult to change parent without breaking children
- Forces "is-a" relationship that may not fit
### When to Use Inheritance:
- True "is-a" relationship (rare)
- Framework requirements
- Template Method pattern (intentional)
### Prefer Composition:
```typescript
// BAD: Inheritance
class PremiumUser extends User {
getDiscount(): number { return 20; }
}
// GOOD: Composition
class User {
constructor(private discountPolicy: DiscountPolicy) {}
getDiscount(): number {
return this.discountPolicy.calculate();
}
}
// Now discount behavior is pluggable
new User(new PremiumDiscount());
new User(new StandardDiscount());
new User(new NoDiscount());
```
---
## The Law of Demeter (Principle of Least Knowledge)
**Only talk to your immediate friends.**
A method should only call:
1. Methods on `this`
2. Methods on parameters
3. Methods on objects it creates
4. Methods on its direct components
```typescript
// BAD: Reaching through objects
order.getCustomer().getAddress().getCity();
// GOOD: Ask the immediate friend
order.getShippingCity();
```
This reduces coupling - changes to `Address` don't ripple through all callers.
---
## Encapsulation
**Hide internal details, expose behavior.**
### Levels of Encapsulation:
1. **Data** - private fields, no direct access
2. **Implementation** - how things work internally
3. **Type** - concrete class hidden behind interface
4. **Design** - architectural decisions hidden from clients
```typescript
// BAD: Exposed internals
class Order {
public items: Item[] = [];
public total: number = 0;
}
// Client can corrupt state
order.items.push(item);
order.total = -999; // Oops!
// GOOD: Encapsulated
class Order {
private items: OrderItems;
private total: Money;
addItem(item: Item): void {
this.items.add(item);
this.recalculateTotal();
}
getTotal(): Money {
return this.total; // Returns copy or immutable
}
}
```
---
## Polymorphism
**Replace conditionals with types.**
```typescript
// BAD: Type checking
function calculateShipping(method: string, value: number): number {
if (method === 'standard') return value < 50 ? 5 : 0;
if (method === 'express') return 15;
if (method === 'overnight') return 25;
throw new Error('Unknown method');
}
// GOOD: Polymorphism
interface ShippingMethod {
calculateCost(orderValue: number): number;
}
class StandardShipping implements ShippingMethod {
calculateCost(orderValue: number): number {
return orderValue < 50 ? 5 : 0;
}
}
class ExpressShipping implements ShippingMethod {
calculateCost(orderValue: number): number {
return 15;
}
}
// Usage - no conditionals
function calculateShipping(method: ShippingMethod, value: number): number {
return method.calculateCost(value);
}
```
---
## Value Objects vs Entities
### Value Objects
- Defined by their attributes (no identity)
- Immutable
- Comparable by value
- Examples: `Money`, `Email`, `Address`, `DateRange`
```typescript
class Money {
constructor(
private readonly amount: number,
private readonly currency: string
) {}
equals(other: Money): boolean {
return this.amount === other.amount &&
this.currency === other.currency;
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new CurrencyMismatch();
}
return new Money(this.amount + other.amount, this.currency);
}
}
```
### Entities
- Have identity (survives attribute changes)
- Usually mutable (via methods)
- Comparable by identity
- Examples: `User`, `Order`, `Product`
```typescript
class User {
constructor(
private readonly id: UserId,
private email: Email,
private name: Name
) {}
equals(other: User): boolean {
return this.id.equals(other.id); // Identity comparison
}
changeEmail(newEmail: Email): void {
this.email = newEmail; // Still same user
}
}
```
---
## Aggregates
A cluster of objects treated as a single unit for data changes.
- One object is the **aggregate root** (entry point)
- External code only references the root
- Root enforces invariants for the entire cluster
```typescript
// Order is the aggregate root
class Order {
private items: OrderItem[] = [];
// All access through the root
addItem(product: Product, quantity: number): void {
const item = new OrderItem(product, quantity);
this.items.push(item);
this.validateTotal();
}
removeItem(itemId: ItemId): void {
this.items = this.items.filter(i => !i.id.equals(itemId));
}
// Root enforces invariants
private validateTotal(): void {
if (this.calculateTotal().exceeds(MAX_ORDER_VALUE)) {
throw new OrderTotalExceeded();
}
}
}
// BAD: Accessing items directly
order.items.push(new OrderItem(...)); // Bypasses validation!
// GOOD: Through the root
order.addItem(product, 2); // Validation happens
```
+262
View File
@@ -0,0 +1,262 @@
# SOLID Principles
## Overview
SOLID helps structure software to be flexible, maintainable, and testable. These principles reduce coupling and increase cohesion.
## S - Single Responsibility Principle (SRP)
> "A class should have one, and only one, reason to change."
### Problem It Solves
God objects that do everything - hard to test, hard to change, hard to understand.
### How to Apply
Each class handles ONE responsibility. If you find yourself saying "and" when describing what a class does, split it.
```typescript
// BAD: Multiple responsibilities
class Order {
calculateTotal(): number { ... }
saveToDatabase(): void { ... } // Persistence
generateInvoice(): string { ... } // Presentation
}
// GOOD: Single responsibility each
class Order {
private items: OrderItem[] = [];
addItem(item: OrderItem): void { ... }
calculateTotal(): number { ... }
}
class OrderRepository {
save(order: Order): Promise<void> { ... }
}
class InvoiceGenerator {
generate(order: Order): Invoice { ... }
}
```
### Detection Questions
- Does this class have multiple reasons to change?
- Can I describe it without using "and"?
- Would different stakeholders request changes to different parts?
---
## O - Open/Closed Principle (OCP)
> "Software entities should be open for extension but closed for modification."
### Problem It Solves
Having to modify existing, tested code every time requirements change. Risk of breaking working features.
### How to Apply
Design abstractions that allow new behavior through new classes, not edits to existing ones.
```typescript
// BAD: Must modify to add new shipping
class ShippingCalculator {
calculate(type: string, value: number): number {
if (type === 'standard') return value < 50 ? 5 : 0;
if (type === 'express') return 15;
// Must add more ifs for new types!
}
}
// GOOD: Open for extension
interface ShippingMethod {
calculateCost(orderValue: number): number;
}
class StandardShipping implements ShippingMethod {
calculateCost(orderValue: number): number {
return orderValue < 50 ? 5 : 0;
}
}
class ExpressShipping implements ShippingMethod {
calculateCost(orderValue: number): number {
return 15;
}
}
// Add new shipping by creating new class, not modifying existing
class SameDayShipping implements ShippingMethod {
calculateCost(orderValue: number): number {
return 25;
}
}
```
### Architectural Insight
OCP at architecture level means: **design your codebase so new features are added by adding code, not changing existing code.**
---
## L - Liskov Substitution Principle (LSP)
> "Subtypes must be substitutable for their base types without altering program correctness."
### Problem It Solves
Subclasses that break expectations, requiring type-checking and special cases.
### How to Apply
Subclasses must honor the contract of the parent. If the parent returns positive numbers, subclasses cannot return negatives.
```typescript
// BAD: Violates parent's contract
class DiscountPolicy {
getDiscount(value: number): number {
return 0; // Non-negative expected
}
}
class WeirdDiscount extends DiscountPolicy {
getDiscount(value: number): number {
return -5; // Increases cost! Breaks expectations
}
}
// GOOD: Enforces contract
class DiscountPolicy {
constructor(private discount: number) {
if (discount < 0) throw new Error("Discount must be non-negative");
}
getDiscount(): number {
return this.discount;
}
}
```
### Key Insight
This is why you can swap `InMemoryUserRepo` for `PostgresUserRepo` - they both honor the `UserRepo` interface contract.
---
## I - Interface Segregation Principle (ISP)
> "Clients should not be forced to depend on methods they do not use."
### Problem It Solves
Fat interfaces that force partial implementations, empty methods, or throws.
### How to Apply
Split large interfaces into smaller, cohesive ones. Clients depend only on what they need.
```typescript
// BAD: Fat interface
interface WarehouseDevice {
printLabel(orderId: string): void;
scanBarcode(): string;
packageItem(orderId: string): void;
}
class BasicPrinter implements WarehouseDevice {
printLabel(orderId: string): void { /* works */ }
scanBarcode(): string { throw new Error("Not supported"); } // Forced!
packageItem(orderId: string): void { throw new Error("Not supported"); }
}
// GOOD: Segregated interfaces
interface LabelPrinter {
printLabel(orderId: string): void;
}
interface BarcodeScanner {
scanBarcode(): string;
}
interface ItemPackager {
packageItem(orderId: string): void;
}
class BasicPrinter implements LabelPrinter {
printLabel(orderId: string): void { /* only what it does */ }
}
```
### Detection
If you see `throw new Error("Not implemented")` or empty method bodies, the interface is too fat.
---
## D - Dependency Inversion Principle (DIP)
> "High-level modules should not depend on low-level modules. Both should depend on abstractions."
### Problem It Solves
Tight coupling to specific implementations (databases, APIs, frameworks). Hard to test, hard to swap.
### How to Apply
Depend on interfaces, inject implementations.
```typescript
// BAD: Direct dependency on concrete class
class OrderService {
private emailService = new SendGridEmailService(); // Locked in!
confirmOrder(email: string): void {
this.emailService.send(email, "Order confirmed");
}
}
// GOOD: Depend on abstraction
interface EmailService {
send(to: string, message: string): void;
}
class OrderService {
constructor(private emailService: EmailService) {}
confirmOrder(email: string): void {
this.emailService.send(email, "Order confirmed");
}
}
// Now can inject any implementation
new OrderService(new SendGridEmailService());
new OrderService(new SESEmailService());
new OrderService(new MockEmailService()); // For tests!
```
### The Dependency Rule
Source code dependencies should point **inward** toward high-level policies (domain logic), never toward low-level details (infrastructure).
```
Infrastructure → Application → Domain
↑ ↑ ↑
(outer) (middle) (inner)
Dependencies flow: outer → inner
Never: inner → outer
```
---
## Applying SOLID at Architecture Level
These principles scale beyond classes:
| Principle | Architecture Application |
|-----------|--------------------------|
| SRP | Each bounded context has one responsibility |
| OCP | New features = new modules, not edits to existing |
| LSP | Microservices with same contract are substitutable |
| ISP | Thin interfaces between services |
| DIP | High-level business logic doesn't know about databases/frameworks |
---
## Quick Reference
| Principle | One-Liner | Red Flag |
|-----------|-----------|----------|
| SRP | One reason to change | "This class handles X and Y and Z" |
| OCP | Add, don't modify | `if/else` chains for types |
| LSP | Subtypes are substitutable | Type-checking in calling code |
| ISP | Small, focused interfaces | Empty method implementations |
| DIP | Depend on abstractions | `new ConcreteClass()` in business logic |
+152
View File
@@ -0,0 +1,152 @@
# Test-Driven Development
## The Core Loop
```
RED → GREEN → REFACTOR → RED → ...
```
### RED Phase
Write a failing test that describes the behavior you want. The test should:
- Use domain language, not technical jargon
- Describe WHAT, not HOW
- Be a concrete example, not an abstract statement
```typescript
// BAD: Abstract
it('can add numbers', () => { ... });
// GOOD: Concrete example
it('when adding 2 + 3, returns 5', () => { ... });
```
### GREEN Phase
Write the **simplest possible code** to make the test pass. Two strategies:
1. **Fake It** - Return a hardcoded value
```typescript
add(a: number, b: number): number {
return 5; // Simplest thing!
}
```
2. **Obvious Implementation** - If you know the solution
```typescript
add(a: number, b: number): number {
return a + b;
}
```
**Prefer Fake It** when learning or unsure. Let more tests drive the real implementation.
### REFACTOR Phase
This is where **design happens**. Look for:
- Duplication (but wait for Rule of Three)
- Long methods to extract
- Poor names to improve
- Complex conditions to simplify
## The Three Laws of TDD
1. **No production code** without a failing test
2. **No more test code** than sufficient to fail (compilation failures count)
3. **No more production code** than sufficient to pass the one failing test
## The Rule of Three
**Only extract duplication when you see it THREE times.**
Why? Wrong abstractions are worse than duplication. Wait for the pattern to emerge.
```typescript
// Duplication #1 - Leave it
// Duplication #2 - Note it, leave it
// Duplication #3 - NOW extract it
```
## Triangulation
Each new test "sculpts" the solution toward a general, robust implementation.
Think of **degrees of freedom** - like a car that needs forward/back, left/right, and rotation. Each test carves out one degree of freedom until the implementation handles all cases.
## Transformation Priority Premise
When going from RED to GREEN, prefer simpler transformations:
| Priority | Transformation |
|----------|----------------|
| 1 | {} → nil |
| 2 | nil → constant |
| 3 | constant → variable |
| 4 | unconditional → conditional |
| 5 | scalar → collection |
| 6 | statement → recursion |
| 7 | value → mutated value |
Higher priority = simpler. Avoid jumping to complex transformations too early.
## Arrange-Act-Assert
Structure every test:
```typescript
it('calculates total with discount', () => {
// ARRANGE - Set up the world
const order = new Order();
order.addItem({ price: 100 });
const discount = new PercentDiscount(10);
// ACT - Execute the behavior
const total = order.calculateTotal(discount);
// ASSERT - Verify the outcome
expect(total).toBe(90);
});
```
## Writing Tests Backwards
Sometimes it helps to write AAA in reverse:
1. Write the ASSERT first - what do you want to verify?
2. Write the ACT - what action produces that result?
3. Write the ARRANGE - what setup is needed?
## Test Naming Principles
- Use **behavior-driven names** with domain language
- Provide **concrete examples**, not abstract statements
- **One example per test** for easy debugging
- Avoid leaking implementation details
```typescript
// BAD: Technical, implementation-focused
it('should set the data property to 1', () => { ... });
// GOOD: Behavior-focused, domain language
it('should recognize "mom" as a palindrome', () => { ... });
```
## Classic vs Mockist TDD
**Classic (Detroit/Chicago) TDD:**
- Test with real dependencies
- Higher confidence, slower tests
- Best for: Pure functions, integration tests
**Mockist (London) TDD:**
- Mock external dependencies
- Faster tests, more isolated
- Best for: Classes with infrastructure dependencies
Start with Classic TDD to learn the technique. Add mocks when testing code with databases, APIs, etc.
## Common Mistakes
1. **Writing code before tests** - Violates the fundamental principle
2. **Writing too much test** - Just enough to fail
3. **Writing too much code** - Just enough to pass
4. **Skipping refactor** - This is where design lives
5. **Testing implementation** - Test behavior, not how it's done
6. **Abstract test names** - Use concrete examples
7. **Extracting too early** - Wait for Rule of Three
+396
View File
@@ -0,0 +1,396 @@
# Testing Strategy
## The Testing Pyramid
```
/\
/ \ E2E Tests (Few)
/----\ - Full system
/ \ - Slow, brittle
/--------\
/ \ Integration Tests (Some)
/------------\ - Multiple components
/ \ - Medium speed
----------------
Unit Tests (Many)
- Single unit
- Fast, isolated
```
## Test Types
### Unit Tests
Test ONE class or function in isolation.
**Characteristics:**
- Fast (milliseconds)
- No external dependencies (mocked)
- Most of your tests should be unit tests
```typescript
describe('Order', () => {
it('calculates total correctly', () => {
const order = new Order();
order.addItem({ price: 100 });
order.addItem({ price: 50 });
expect(order.calculateTotal()).toBe(150);
});
});
```
### Integration Tests
Test multiple components together.
**Characteristics:**
- Slower (may use real DB)
- Test boundaries between components
- Fewer than unit tests
```typescript
describe('OrderService Integration', () => {
let db: Database;
let service: OrderService;
beforeAll(async () => {
db = await Database.connect();
service = new OrderService(new PostgresOrderRepo(db));
});
it('saves and retrieves an order', async () => {
const order = Order.create({ customerId: '123' });
await service.save(order);
const retrieved = await service.findById(order.id);
expect(retrieved).toEqual(order);
});
});
```
### E2E / Acceptance Tests
Test the entire system from user perspective.
**Characteristics:**
- Slowest
- Most brittle (many moving parts)
- Test critical paths only
```typescript
describe('Checkout Flow', () => {
it('user can complete purchase', async () => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('[name="card"]', '4242424242424242');
await page.click('[data-testid="pay"]');
expect(await page.textContent('h1')).toBe('Order Confirmed');
});
});
```
---
## Arrange-Act-Assert (AAA)
Structure EVERY test this way:
```typescript
it('applies discount to premium users', () => {
// ARRANGE - Set up the test world
const user = new User({ isPremium: true });
const cart = new Cart(user);
cart.addItem({ price: 100 });
// ACT - Execute the behavior under test
const total = cart.calculateTotal();
// ASSERT - Verify the expected outcome
expect(total).toBe(80); // 20% discount
});
```
### Writing AAA Backwards
Sometimes easier to write in reverse:
1. **Assert first** - What do you want to verify?
2. **Act** - What action produces that result?
3. **Arrange** - What setup is needed?
---
## Test Naming
### Bad: Abstract, Technical
```typescript
it('should work correctly')
it('handles the edge case')
it('sets the data property')
```
### Good: Concrete Examples, Domain Language
```typescript
it('calculates 20% discount for premium users')
it('returns error when cart is empty')
it('recognizes "racecar" as a palindrome')
```
### Format
```typescript
// Option 1: should + behavior
it('should apply tax based on shipping state')
// Option 2: when + then
it('when adding 2 + 3, then returns 5')
// Option 3: Given-When-Then (for complex scenarios)
describe('given a premium user', () => {
describe('when they checkout', () => {
it('then they receive 20% discount', () => { ... });
});
});
```
---
## Test Doubles
### Dummy
Object passed but never used.
```typescript
const dummyLogger = {} as Logger;
new UserService(realRepo, dummyLogger);
```
### Stub
Returns predefined values.
```typescript
const stubRepo: UserRepo = {
findById: () => Promise.resolve(new User({ name: 'Test' })),
save: () => Promise.resolve(),
};
```
### Spy
Records how it was called.
```typescript
const emailSpy = {
sentEmails: [] as string[],
send(to: string, message: string) {
this.sentEmails.push(to);
}
};
// Later
expect(emailSpy.sentEmails).toContain('user@example.com');
```
### Mock
Verifies expected interactions.
```typescript
const mockRepo = jest.fn<UserRepo>();
mockRepo.save.mockResolvedValue(undefined);
// After test
expect(mockRepo.save).toHaveBeenCalledWith(expectedUser);
```
### Fake
Working implementation (simplified).
```typescript
class InMemoryUserRepo implements UserRepo {
private users: Map<string, User> = new Map();
async save(user: User): Promise<void> {
this.users.set(user.id, user);
}
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null;
}
}
```
---
## Testing Strategies by Layer
### Domain Layer (Most Tests)
- Unit tests with no mocks
- Test business rules, value objects, entities
- Fast, comprehensive
```typescript
describe('Money', () => {
it('adds amounts with same currency', () => {
const a = Money.dollars(10);
const b = Money.dollars(20);
expect(a.add(b).equals(Money.dollars(30))).toBe(true);
});
it('throws when adding different currencies', () => {
const usd = Money.dollars(10);
const eur = Money.euros(10);
expect(() => usd.add(eur)).toThrow(CurrencyMismatch);
});
});
```
### Application Layer
- Integration tests with mocked infrastructure
- Test use case orchestration
```typescript
describe('CreateOrderUseCase', () => {
it('creates order and sends confirmation', async () => {
const orderRepo = new InMemoryOrderRepo();
const emailService = { send: jest.fn() };
const useCase = new CreateOrderUseCase(orderRepo, emailService);
await useCase.execute({ customerId: '123', items: [...] });
expect(orderRepo.count()).toBe(1);
expect(emailService.send).toHaveBeenCalled();
});
});
```
### Infrastructure Layer
- Integration tests with real dependencies
- Test database, API integrations
```typescript
describe('PostgresOrderRepo', () => {
let repo: PostgresOrderRepo;
beforeAll(async () => {
repo = new PostgresOrderRepo(testDb);
});
it('persists and retrieves order', async () => {
const order = Order.create({ ... });
await repo.save(order);
const found = await repo.findById(order.id);
expect(found).toEqual(order);
});
});
```
---
## High-Value Integration Tests
Focus integration tests on:
1. **Boundaries** - Where systems meet
2. **Critical paths** - Money, security, core features
3. **Complex queries** - Database operations
### Contract Tests
Verify implementations match interfaces.
```typescript
// Shared contract test
function testUserRepoContract(createRepo: () => UserRepo) {
describe('UserRepo Contract', () => {
let repo: UserRepo;
beforeEach(() => {
repo = createRepo();
});
it('saves and retrieves user', async () => {
const user = User.create({ name: 'Test' });
await repo.save(user);
const found = await repo.findById(user.id);
expect(found).toEqual(user);
});
it('returns null for missing user', async () => {
const found = await repo.findById('nonexistent');
expect(found).toBeNull();
});
});
}
// Apply to all implementations
testUserRepoContract(() => new InMemoryUserRepo());
testUserRepoContract(() => new PostgresUserRepo(testDb));
```
---
## Test Builders
Create test objects easily.
```typescript
class OrderBuilder {
private props: Partial<OrderProps> = {
id: 'order-1',
customerId: 'cust-1',
items: [],
status: 'pending',
};
withId(id: string): OrderBuilder {
this.props.id = id;
return this;
}
withItems(items: Item[]): OrderBuilder {
this.props.items = items;
return this;
}
paid(): OrderBuilder {
this.props.status = 'paid';
return this;
}
build(): Order {
return Order.create(this.props as OrderProps);
}
}
// Usage
const order = new OrderBuilder()
.withItems([{ sku: 'ABC', price: 100 }])
.paid()
.build();
```
---
## Common Testing Mistakes
| Mistake | Problem | Solution |
|---------|---------|----------|
| Testing implementation | Brittle tests | Test behavior only |
| Too many mocks | Tests prove nothing | Use real objects when possible |
| Shared state | Flaky tests | Isolate each test |
| No assertions | False confidence | Always assert something meaningful |
| Testing trivial code | Wasted effort | Focus on logic and edge cases |
| Slow tests | Reduced feedback | Optimize, use unit tests |