feat: adding new skills, including testing patterns and methodologies, along with bundled resources for better usability.

This commit is contained in:
duthaho
2026-03-30 12:18:00 +07:00
parent 0ff5ae4082
commit 7fa9a48c6c
89 changed files with 25808 additions and 923 deletions
+677 -54
View File
@@ -1,101 +1,724 @@
---
name: javascript
description: >
Trigger this skill whenever working with JavaScript files (.js, .mjs, .cjs), writing Node.js applications without TypeScript, or using ES6+ patterns like destructuring, async/await, optional chaining, and modules. Activate for browser scripting, vanilla JS projects, or when the user asks about JavaScript-specific idioms, ESLint configuration, or modern syntax. Also use when dealing with package.json scripts, CommonJS vs ESM, or JavaScript class patterns.
---
# JavaScript
## Description
Modern JavaScript (ES6+) patterns and best practices for Node.js and browser environments.
## When to Use
- Working with JavaScript files (.js, .mjs)
- Browser scripting
- Node.js applications without TypeScript
## When NOT to Use
- TypeScript projects -- use the `languages/typescript` skill instead, which covers typed JavaScript patterns
- Python-only projects with no JavaScript components
---
## Core Patterns
### Modern Syntax
### 1. Modern Syntax
#### Destructuring (Nested, Defaults, Rest)
```javascript
// Destructuring
const { name, email } = user;
const [first, ...rest] = items;
// Object destructuring with defaults and rename
const { name, email, role = "user", address: { city } = {} } = user;
// Spread operator
const merged = { ...defaults, ...options };
const combined = [...array1, ...array2];
// Nested destructuring
const {
data: {
attributes: { title, body },
},
} = apiResponse;
// Template literals
const message = `Hello, ${name}!`;
// Array destructuring with rest
const [first, second, ...remaining] = items;
// Optional chaining and nullish coalescing
const city = user?.address?.city ?? 'Unknown';
// Swap variables
let a = 1, b = 2;
[a, b] = [b, a];
// Function parameter destructuring
function createUser({ name, email, role = "user" }) {
return { name, email, role, createdAt: new Date() };
}
```
### Async Patterns
#### Optional Chaining (?.)
```javascript
// Async/await
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) throw new Error('Fetch failed');
return response.json();
// Property access
const city = user?.address?.city;
// Method call
const uppercased = value?.toString?.();
// Array element
const firstItem = data?.items?.[0];
// Combine with nullish coalescing for defaults
const displayName = user?.profile?.displayName ?? user?.name ?? "Anonymous";
```
#### Nullish Coalescing (??)
```javascript
// Only falls through on null/undefined (not 0, "", false)
const port = config.port ?? 3000;
const name = input ?? "default";
// Contrast with || which falls through on all falsy values
const count = data.count ?? 0; // preserves 0
const count2 = data.count || 0; // replaces 0 with 0 (same here, but misleading)
const label = data.label ?? ""; // preserves ""
const label2 = data.label || "fallback"; // replaces "" with "fallback"
```
#### Logical Assignment (&&=, ||=, ??=)
```javascript
// ??= assigns only if null/undefined
user.name ??= "Anonymous";
// ||= assigns if falsy
config.retries ||= 3;
// &&= assigns only if truthy
user.session &&= refreshSession(user.session);
// Practical: initialize nested objects
const cache = {};
(cache.users ??= []).push(newUser);
```
---
### 2. Async Patterns
#### Promises
```javascript
function fetchJson(url) {
return fetch(url)
.then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
});
}
// Promise.all for parallel
const results = await Promise.all([
fetchData(url1),
fetchData(url2),
// Chaining
fetchJson("/api/user")
.then((user) => fetchJson(`/api/posts?userId=${user.id}`))
.then((posts) => console.log(posts))
.catch((error) => console.error("Failed:", error.message));
```
#### async/await
```javascript
async function loadUserDashboard(userId) {
const user = await fetchJson(`/api/users/${userId}`);
const posts = await fetchJson(`/api/users/${userId}/posts`);
return { user, posts };
}
```
#### Promise.all / allSettled / race / any
```javascript
// Promise.all -- fail fast on first rejection
const [users, posts, comments] = await Promise.all([
fetchJson("/api/users"),
fetchJson("/api/posts"),
fetchJson("/api/comments"),
]);
// Error handling
try {
const data = await fetchData(url);
} catch (error) {
console.error('Failed:', error.message);
// Promise.allSettled -- wait for all, get status of each
const results = await Promise.allSettled([
fetchJson("/api/fast"),
fetchJson("/api/slow"),
fetchJson("/api/flaky"),
]);
const successes = results
.filter((r) => r.status === "fulfilled")
.map((r) => r.value);
const failures = results
.filter((r) => r.status === "rejected")
.map((r) => r.reason);
// Promise.race -- first to settle wins
const result = await Promise.race([
fetchJson("/api/primary"),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 5000)
),
]);
// Promise.any -- first to fulfill wins (ignores rejections)
const fastest = await Promise.any([
fetchJson("/api/mirror1"),
fetchJson("/api/mirror2"),
fetchJson("/api/mirror3"),
]);
```
#### AbortController
```javascript
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
// Cancellable request pattern
function createRequest(url) {
const controller = new AbortController();
return {
promise: fetch(url, { signal: controller.signal }),
cancel: () => controller.abort(),
};
}
```
### Array Methods
#### Async Iterators (for await...of)
```javascript
// Map, filter, reduce
const names = users.map(u => u.name);
const active = users.filter(u => u.active);
const total = items.reduce((sum, i) => sum + i.price, 0);
async function* paginateApi(baseUrl) {
let page = 1;
while (true) {
const data = await fetchJson(`${baseUrl}?page=${page}`);
if (data.items.length === 0) break;
yield* data.items;
page++;
}
}
// Find and includes
const user = users.find(u => u.id === id);
const hasAdmin = users.some(u => u.role === 'admin');
// Consume the async iterator
for await (const item of paginateApi("/api/records")) {
processItem(item);
}
```
### Classes
---
### 3. Closures & Scope
#### Closure Patterns
```javascript
class UserService {
#db; // Private field
// Counter with private state
function createCounter(initial = 0) {
let count = initial;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
reset: () => { count = initial; },
};
}
constructor(database) {
this.#db = database;
const counter = createCounter(10);
counter.increment(); // 11
counter.getCount(); // 11
```
#### Module Pattern (Private State via Closures)
```javascript
const rateLimiter = (() => {
const requests = new Map();
function isAllowed(clientId, maxPerMinute = 60) {
const now = Date.now();
const windowStart = now - 60_000;
const clientRequests = (requests.get(clientId) ?? []).filter(
(t) => t > windowStart
);
if (clientRequests.length >= maxPerMinute) return false;
clientRequests.push(now);
requests.set(clientId, clientRequests);
return true;
}
async findById(id) {
return this.#db.users.find(u => u.id === id);
function reset(clientId) {
requests.delete(clientId);
}
return { isAllowed, reset };
})();
```
#### WeakRef and FinalizationRegistry
```javascript
// Cache that does not prevent garbage collection
const cache = new Map();
function getCached(key, factory) {
const ref = cache.get(key);
const cached = ref?.deref();
if (cached !== undefined) return cached;
const value = factory();
cache.set(key, new WeakRef(value));
return value;
}
// Cleanup when objects are garbage collected
const registry = new FinalizationRegistry((key) => {
cache.delete(key);
});
```
---
### 4. Iteration Protocols
#### Custom Iterator
```javascript
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current < end) {
const value = current;
current += step;
return { value, done: false };
}
return { done: true };
},
};
}
}
for (const n of new Range(0, 10, 2)) {
console.log(n); // 0, 2, 4, 6, 8
}
```
#### Generators
```javascript
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Take first N values
function take(iterable, count) {
const result = [];
for (const value of iterable) {
result.push(value);
if (result.length >= count) break;
}
return result;
}
take(fibonacci(), 8); // [0, 1, 1, 2, 3, 5, 8, 13]
```
#### Lazy Evaluation with Generators
```javascript
function* map(iterable, fn) {
for (const item of iterable) {
yield fn(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) yield item;
}
}
// Compose lazily -- no intermediate arrays
const data = filter(
map(readLargeFile(), (line) => line.trim()),
(line) => line.length > 0
);
```
#### Async Generators
```javascript
async function* readChunks(reader) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield value;
}
}
// Stream processing
const response = await fetch("/api/large-data");
for await (const chunk of readChunks(response.body.getReader())) {
processChunk(chunk);
}
```
---
### 5. Proxy & Reflect
#### Validation Proxy
```javascript
function createValidated(target, validators) {
return new Proxy(target, {
set(obj, prop, value) {
const validate = validators[prop];
if (validate && !validate(value)) {
throw new TypeError(`Invalid value for ${String(prop)}: ${value}`);
}
return Reflect.set(obj, prop, value);
},
});
}
const user = createValidated(
{ name: "", age: 0 },
{
name: (v) => typeof v === "string" && v.length > 0,
age: (v) => typeof v === "number" && v >= 0 && v <= 150,
}
);
user.name = "Alice"; // works
user.age = -1; // throws TypeError
```
#### Observable Object
```javascript
function createObservable(target, onChange) {
return new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop];
const result = Reflect.set(obj, prop, value);
if (oldValue !== value) {
onChange(prop, value, oldValue);
}
return result;
},
deleteProperty(obj, prop) {
const oldValue = obj[prop];
const result = Reflect.deleteProperty(obj, prop);
onChange(prop, undefined, oldValue);
return result;
},
});
}
const state = createObservable({}, (prop, newVal, oldVal) => {
console.log(`${prop}: ${oldVal} -> ${newVal}`);
});
```
#### Property Access Logging
```javascript
function withLogging(target, label = "access") {
return new Proxy(target, {
get(obj, prop) {
console.log(`[${label}] get .${String(prop)}`);
return Reflect.get(obj, prop);
},
has(obj, prop) {
console.log(`[${label}] has .${String(prop)}`);
return Reflect.has(obj, prop);
},
});
}
```
---
### 6. Module System
#### ESM (import/export)
```javascript
// Named exports
export function formatDate(date) { ... }
export const MAX_RETRIES = 3;
// Default export
export default class ApiClient { ... }
// Re-exports
export { formatDate } from "./utils.js";
export { default as ApiClient } from "./api-client.js";
```
#### Dynamic import()
```javascript
// Lazy load modules
async function loadChart(type) {
const module = await import(`./charts/${type}.js`);
return new module.default();
}
// Conditional loading
const { marked } = await import("marked");
// With error handling
async function tryLoadPlugin(name) {
try {
return await import(`./plugins/${name}.js`);
} catch {
console.warn(`Plugin ${name} not available`);
return null;
}
}
```
#### import.meta
```javascript
// Current module URL
console.log(import.meta.url);
// Resolve relative paths (Node.js)
const configPath = new URL("./config.json", import.meta.url);
// Check if file is the entry point (Node.js)
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
// Vite environment variables
const apiUrl = import.meta.env.VITE_API_URL;
```
#### Top-level await
```javascript
// config.js -- top-level await in ESM modules
const response = await fetch("/api/config");
export const config = await response.json();
// db.js
import { createPool } from "./db-pool.js";
export const pool = await createPool(process.env.DATABASE_URL);
```
---
### 7. Performance
#### structuredClone
```javascript
// Deep clone without library (replaces JSON.parse(JSON.stringify(...)))
const original = { nested: { array: [1, 2, 3], date: new Date() } };
const clone = structuredClone(original);
// Handles Date, Map, Set, ArrayBuffer, RegExp (but not functions)
clone.nested.array.push(4);
console.log(original.nested.array.length); // still 3
```
#### requestAnimationFrame
```javascript
// Smooth animation loop
function animate(timestamp) {
updatePosition(timestamp);
render();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// Throttle DOM updates to frame rate
let rafId = null;
function scheduleUpdate(data) {
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
applyDOMUpdate(data);
rafId = null;
});
}
```
#### requestIdleCallback
```javascript
// Run low-priority work when the browser is idle
function processQueue(queue) {
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && queue.length > 0) {
const task = queue.shift();
task();
}
if (queue.length > 0) {
processQueue(queue); // schedule remaining
}
});
}
```
#### Web Workers Basics
```javascript
// main.js
const worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
worker.postMessage({ data: largeDataSet, operation: "sort" });
worker.onmessage = (event) => {
const sorted = event.data;
renderResults(sorted);
};
// worker.js
self.onmessage = (event) => {
const { data, operation } = event.data;
if (operation === "sort") {
self.postMessage(data.sort((a, b) => a - b));
}
};
```
#### performance.mark / measure
```javascript
// Measure operation duration
performance.mark("fetch-start");
const data = await fetchJson("/api/data");
performance.mark("fetch-end");
performance.measure("fetch-duration", "fetch-start", "fetch-end");
const measurement = performance.getEntriesByName("fetch-duration")[0];
console.log(`Fetch took ${measurement.duration.toFixed(2)}ms`);
// Clean up
performance.clearMarks();
performance.clearMeasures();
```
---
## Best Practices
1. Use `const` by default, `let` when needed
2. Avoid `var` - use block-scoped declarations
3. Use arrow functions for callbacks
4. Handle all promise rejections
5. Use ESLint for consistent style
1. **Use `const` by default, `let` only when reassignment is needed** -- never use `var`. Block scoping prevents entire categories of bugs from hoisting and accidental mutation.
2. **Handle all promise rejections** -- unhandled rejections crash Node.js processes. Always use try/catch with await, or attach `.catch()` to promise chains. Add a global handler as a safety net.
```javascript
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection:", reason);
process.exit(1);
});
```
3. **Use arrow functions for callbacks, regular functions for methods** -- arrow functions capture `this` from the enclosing scope, which is correct for callbacks but breaks object methods that need their own `this`.
4. **Prefer `for...of` over `for...in` for iteration** -- `for...in` iterates over all enumerable properties including inherited ones. Use `for...of` for arrays and iterables, `Object.entries()` for objects.
5. **Use ESLint and Prettier** -- enforce consistent style automatically. Configure in the project root and run on pre-commit hooks.
6. **Avoid mutating function arguments** -- create new objects and arrays with spread syntax instead of modifying inputs in place. This prevents action-at-a-distance bugs.
7. **Use `structuredClone` for deep copies** -- replaces the `JSON.parse(JSON.stringify(x))` hack. Handles Dates, Maps, Sets, and circular references correctly.
8. **Use private class fields (`#field`)** -- the `#` prefix creates truly private fields that cannot be accessed outside the class, unlike the `_` convention which is only a hint.
---
## Common Pitfalls
- **Implicit type coercion**: Use `===` instead of `==`
- **Callback hell**: Use async/await
- **Mutating objects**: Create new objects with spread
- **Not handling errors**: Always catch promise rejections
1. **Implicit type coercion** -- always use `===` and `!==`. The `==` operator performs type coercion with surprising rules (`"" == false`, `0 == null` is false but `0 == undefined` is also false, yet `null == undefined` is true).
2. **Forgetting `await`** -- a missing `await` silently returns a Promise object instead of the resolved value, causing hard-to-debug issues.
```javascript
// BAD -- data is a Promise, not the response
const data = fetchJson("/api/data");
// GOOD
const data = await fetchJson("/api/data");
```
3. **`this` binding in callbacks** -- regular functions in callbacks lose their `this` context. Use arrow functions or `.bind()`.
```javascript
// BAD
class Timer {
start() { setTimeout(function() { this.tick(); }, 1000); }
}
// GOOD
class Timer {
start() { setTimeout(() => this.tick(), 1000); }
}
```
4. **Mutating objects passed by reference** -- objects and arrays are passed by reference. Modifying a parameter modifies the original.
```javascript
// BAD
function addDefaults(config) {
config.retries = config.retries ?? 3; // mutates caller's object
return config;
}
// GOOD
function addDefaults(config) {
return { retries: 3, ...config };
}
```
5. **`for...in` on arrays** -- iterates over indices as strings and includes inherited properties. Use `for...of` or array methods.
```javascript
// BAD
for (const i in [10, 20, 30]) {
console.log(typeof i); // "string", not "number"
}
// GOOD
for (const value of [10, 20, 30]) {
console.log(value); // 10, 20, 30
}
```
6. **Floating point arithmetic** -- `0.1 + 0.2 !== 0.3` in JavaScript. For financial calculations, work in integer cents or use a decimal library.
```javascript
// BAD
const total = 0.1 + 0.2; // 0.30000000000000004
// GOOD
const totalCents = 10 + 20; // 30
const total = totalCents / 100; // 0.3
```
---
## Related Skills
- `languages/typescript` -- TypeScript for typed JavaScript development
- `frameworks/react` -- React component patterns
- `frameworks/nextjs` -- Next.js full-stack framework
- `testing/vitest` -- JavaScript/TypeScript testing with Vitest
@@ -0,0 +1,247 @@
# Modern JavaScript Patterns Quick Reference
> ES2020+ patterns. All examples work in current Node.js (18+) and modern browsers.
## Destructuring Tricks
```javascript
// Nested destructuring with rename and default
const { data: { users: members = [] } = {} } = response;
// Array destructuring: skip elements
const [first, , third] = [1, 2, 3];
// Swap variables
[a, b] = [b, a];
// Rest in both arrays and objects
const { id, ...rest } = user;
const [head, ...tail] = items;
// Destructure function parameters
function draw({ x = 0, y = 0, color = "black" } = {}) { /* ... */ }
// Dynamic property destructuring
const key = "name";
const { [key]: value } = { name: "Alice" }; // value = "Alice"
// Destructure from iterables
const [a, b] = new Map([["a", 1], ["b", 2]]);
```
## Optional Chaining (?.)
```javascript
// Property access
const city = user?.address?.city;
// Method call (only calls if method exists)
const result = api?.getData?.();
// Bracket notation
const val = obj?.["dynamic-key"];
// Array index
const first = arr?.[0];
// Combine with nullish coalescing
const name = user?.profile?.name ?? "Anonymous";
// Short-circuit: stops evaluating after first nullish
const len = response?.data?.items?.length; // undefined if any is nullish
```
## Nullish Coalescing (??) vs OR (||)
```javascript
// ?? only triggers on null/undefined (NOT 0, "", false)
0 ?? "fallback" // 0
"" ?? "fallback" // ""
null ?? "fallback" // "fallback"
// || triggers on any falsy value
0 || "fallback" // "fallback"
"" || "fallback" // "fallback"
null || "fallback" // "fallback"
// Use ?? for values where 0/empty string are valid
const port = config.port ?? 3000;
const title = config.title ?? "Untitled";
```
## Logical Assignment Operators
```javascript
// ??= assigns only if current value is null/undefined
user.name ??= "Anonymous";
// Equivalent: user.name = user.name ?? "Anonymous"
// ||= assigns if current value is falsy
opts.verbose ||= false;
// Equivalent: opts.verbose = opts.verbose || false
// &&= assigns only if current value is truthy
user.token &&= encrypt(user.token);
// Equivalent: user.token = user.token && encrypt(user.token)
```
## structuredClone (Deep Copy)
```javascript
// Deep clone objects, arrays, Maps, Sets, Dates, RegExp, etc.
const original = { date: new Date(), nested: { arr: [1, 2] } };
const clone = structuredClone(original);
clone.nested.arr.push(3); // original not affected
// Works with circular references
const obj = { self: null };
obj.self = obj;
const copy = structuredClone(obj); // OK
// Does NOT clone: functions, DOM nodes, symbols, prototype chain
// Throws on: functions, Error objects (in some engines)
```
## Proxy
```javascript
// Validation proxy
const validated = new Proxy({}, {
set(target, prop, value) {
if (prop === "age" && (typeof value !== "number" || value < 0)) {
throw new TypeError("Age must be a non-negative number");
}
target[prop] = value;
return true;
}
});
// Read-only proxy
function readonly(target) {
return new Proxy(target, {
set() { throw new Error("Read-only object"); },
deleteProperty() { throw new Error("Read-only object"); }
});
}
// Default values proxy
function withDefaults(target, defaults) {
return new Proxy(target, {
get(obj, prop) {
return prop in obj ? obj[prop] : defaults[prop];
}
});
}
const config = withDefaults({}, { theme: "dark", lang: "en" });
config.theme; // "dark"
// Logging / observation proxy
function observable(target, onChange) {
return new Proxy(target, {
set(obj, prop, value) {
const old = obj[prop];
obj[prop] = value;
onChange(prop, old, value);
return true;
}
});
}
```
## Generators
```javascript
// Basic generator
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
for (const n of range(0, 10, 2)) { /* 0, 2, 4, 6, 8 */ }
// Infinite sequence
function* ids() {
let id = 0;
while (true) yield id++;
}
const gen = ids();
gen.next().value; // 0
gen.next().value; // 1
// Delegate to another generator
function* concat(...iterables) {
for (const it of iterables) {
yield* it;
}
}
// Two-way communication
function* stateMachine() {
let input;
while (true) {
input = yield `received: ${input}`;
}
}
const sm = stateMachine();
sm.next(); // { value: "received: undefined" }
sm.next("hello"); // { value: "received: hello" }
```
## Async Iterators
```javascript
// for-await-of
async function processStream(stream) {
for await (const chunk of stream) {
console.log(chunk);
}
}
// Async generator
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.items.length === 0) return;
yield data.items;
page++;
}
}
for await (const items of fetchPages("/api/users")) {
console.log(items);
}
```
## Other Modern Patterns
```javascript
// Object.groupBy (ES2024)
const grouped = Object.groupBy(users, u => u.role);
// at() - negative indexing
[1, 2, 3].at(-1); // 3
// Object.hasOwn (replaces hasOwnProperty)
Object.hasOwn(obj, "key"); // true/false
// Error cause chaining
throw new Error("DB failed", { cause: originalError });
// AbortSignal.timeout (built-in timeout)
fetch(url, { signal: AbortSignal.timeout(5000) });
// using keyword (explicit resource management, ES2024+)
{ using handle = openFile("data.txt"); } // auto-disposed at block exit
```
## Promise Combinators
| Method | Settles when | Returns |
|--------|-------------|---------|
| `Promise.all(ps)` | All fulfill or one rejects | Array of values |
| `Promise.allSettled(ps)` | All settle | Array of `{status, value/reason}` |
| `Promise.race(ps)` | First settles | First value or rejection |
| `Promise.any(ps)` | First fulfills | First value (AggregateError if all reject) |
+640 -50
View File
@@ -1,9 +1,11 @@
---
name: python
description: >
Trigger this skill whenever working with Python files (.py), writing Python scripts or applications, or using Python frameworks like Django, FastAPI, or Flask. Activate for any Python-specific patterns including type hints, async/await with asyncio, dataclasses, Pydantic models, context managers, virtual environments, or PEP 8 style questions. Also use when the user references Python package management, pip, or pyproject.toml.
---
# Python
## Description
Python development expertise including type hints, async patterns, virtual environments, and Pythonic idioms.
## When to Use
- Working with Python files (.py)
@@ -11,59 +13,148 @@ Python development expertise including type hints, async patterns, virtual envir
- Using Python frameworks (Django, FastAPI, Flask)
- Data processing and automation
## When NOT to Use
- JavaScript or TypeScript-only projects with no Python components
- Non-Python environments where another language skill is more appropriate
---
## Core Patterns
### Type Hints
### 1. Type Hints
Use type hints on all public functions and module-level variables. Python 3.10+ syntax is preferred (use `X | Y` instead of `Union[X, Y]`).
#### Basic Types
```python
from typing import Optional, List, Dict, Union
from collections.abc import Callable
from typing import Any
def process_items(
items: List[str],
callback: Callable[[str], None],
config: Optional[Dict[str, Any]] = None
) -> List[str]:
"""Process items with optional callback."""
return [callback(item) for item in items]
def greet(name: str) -> str:
return f"Hello, {name}"
def process(count: int, factor: float = 1.0) -> float:
return count * factor
def is_valid(data: bytes | None) -> bool:
return data is not None and len(data) > 0
```
### Async/Await
#### Optional and Union
```python
import asyncio
from typing import List
# Python 3.10+ syntax (preferred)
def find_user(user_id: int) -> User | None:
...
async def fetch_data(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# Pre-3.10 fallback
from typing import Optional, Union
async def fetch_all(urls: List[str]) -> List[dict]:
return await asyncio.gather(*[fetch_data(url) for url in urls])
def find_user(user_id: int) -> Optional[User]:
...
def parse_input(value: Union[str, int]) -> str:
return str(value)
```
### Context Managers
#### Generic Collections
```python
from contextlib import contextmanager
# Python 3.9+ built-in generics (preferred)
def process_items(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
@contextmanager
def managed_resource():
resource = acquire_resource()
try:
yield resource
finally:
release_resource(resource)
def merge_configs(base: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]:
return {**base, **overrides}
# Usage
with managed_resource() as r:
r.do_something()
# Nested generics
def group_by_key(pairs: list[tuple[str, int]]) -> dict[str, list[int]]:
result: dict[str, list[int]] = {}
for key, value in pairs:
result.setdefault(key, []).append(value)
return result
```
### Dataclasses
#### Protocol for Structural Subtyping
```python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str: ...
class HtmlWidget:
def render(self) -> str:
return "<div>widget</div>"
def display(item: Renderable) -> None:
print(item.render())
# HtmlWidget satisfies Renderable without inheriting from it
display(HtmlWidget()) # works
```
#### TypeVar for Generic Functions
```python
from typing import TypeVar, Sequence
T = TypeVar("T")
def first(items: Sequence[T]) -> T:
return items[0]
# Bounded TypeVar
Numeric = TypeVar("Numeric", int, float)
def clamp(value: Numeric, low: Numeric, high: Numeric) -> Numeric:
return max(low, min(high, value))
```
#### @overload for Multiple Signatures
```python
from typing import overload
@overload
def parse(raw: str) -> dict[str, Any]: ...
@overload
def parse(raw: bytes) -> dict[str, Any]: ...
@overload
def parse(raw: str, as_list: bool) -> list[Any]: ...
def parse(raw: str | bytes, as_list: bool = False) -> dict[str, Any] | list[Any]:
data = raw if isinstance(raw, str) else raw.decode()
parsed = json.loads(data)
return list(parsed) if as_list else parsed
```
#### TypeAlias and TypeGuard
```python
from typing import TypeAlias, TypeGuard
# TypeAlias for complex types
JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]
Headers: TypeAlias = dict[str, str]
# TypeGuard for narrowing
def is_string_list(val: list[Any]) -> TypeGuard[list[str]]:
return all(isinstance(item, str) for item in val)
def process(items: list[Any]) -> None:
if is_string_list(items):
# items is now list[str] inside this branch
print(", ".join(items))
```
---
### 2. Dataclasses & Pydantic
#### @dataclass with Options
```python
from dataclasses import dataclass, field
@@ -75,36 +166,535 @@ class User:
email: str
name: str
created_at: datetime = field(default_factory=datetime.now)
tags: list[str] = field(default_factory=list)
def __post_init__(self):
self.email = self.email.lower()
self.email = self.email.strip().lower()
```
### Pydantic Models
#### Frozen and Slots
```python
from pydantic import BaseModel, EmailStr, Field
@dataclass(frozen=True, slots=True)
class Coordinate:
"""Immutable, memory-efficient value object."""
x: float
y: float
@property
def magnitude(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
```
#### Pydantic BaseModel
```python
from pydantic import BaseModel, EmailStr, Field, field_validator, computed_field, model_validator
class UserCreate(BaseModel):
model_config = {"str_strip_whitespace": True, "frozen": False}
email: EmailStr
name: str = Field(min_length=1, max_length=100)
password: str = Field(min_length=8)
age: int = Field(ge=0, le=150)
class Config:
str_strip_whitespace = True
@field_validator("name")
@classmethod
def name_must_not_be_blank(cls, v: str) -> str:
if not v.strip():
raise ValueError("Name must not be blank")
return v.title()
@computed_field
@property
def display_name(self) -> str:
return f"{self.name} <{self.email}>"
@model_validator(mode="after")
def check_consistency(self) -> "UserCreate":
if "admin" in self.name.lower() and self.age < 18:
raise ValueError("Admins must be 18+")
return self
```
---
### 3. Async Patterns
#### Basic async/await
```python
import asyncio
import aiohttp
async def fetch_json(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.json()
```
#### asyncio.gather for Parallel Work
```python
async def fetch_all(urls: list[str]) -> list[dict]:
return await asyncio.gather(*[fetch_json(url) for url in urls])
```
#### asyncio.TaskGroup (Python 3.11+)
```python
async def fetch_all_safe(urls: list[str]) -> list[dict]:
results: list[dict] = []
async with asyncio.TaskGroup() as tg:
for url in urls:
tg.create_task(fetch_and_append(url, results))
return results
async def fetch_and_append(url: str, results: list[dict]) -> None:
data = await fetch_json(url)
results.append(data)
```
#### Async Generators
```python
async def paginate(url: str) -> AsyncIterator[dict]:
page = 1
while True:
data = await fetch_json(f"{url}?page={page}")
if not data["items"]:
break
for item in data["items"]:
yield item
page += 1
# Usage
async for item in paginate("/api/users"):
process(item)
```
#### Async Context Managers
```python
from contextlib import asynccontextmanager
@asynccontextmanager
async def db_transaction(pool):
conn = await pool.acquire()
tx = await conn.begin()
try:
yield conn
await tx.commit()
except Exception:
await tx.rollback()
raise
finally:
await pool.release(conn)
```
#### Semaphores for Concurrency Limiting
```python
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10) -> list[dict]:
semaphore = asyncio.Semaphore(max_concurrent)
async def limited_fetch(url: str) -> dict:
async with semaphore:
return await fetch_json(url)
return await asyncio.gather(*[limited_fetch(url) for url in urls])
```
---
### 4. Decorators
#### Function Decorator with functools.wraps
```python
import functools
import time
def timing(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timing
def slow_operation():
time.sleep(1)
```
#### Decorator with Arguments
```python
def retry(max_attempts: int = 3, delay: float = 1.0):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
last_error: Exception | None = None
for attempt in range(max_attempts):
try:
return await func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_attempts - 1:
await asyncio.sleep(delay * (2 ** attempt))
raise last_error
return wrapper
return decorator
@retry(max_attempts=5, delay=0.5)
async def unreliable_call(url: str) -> dict:
return await fetch_json(url)
```
#### Class Decorator
```python
def singleton(cls):
instances: dict[type, Any] = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class AppConfig:
def __init__(self):
self.settings = load_settings()
```
#### Caching Decorator
```python
from functools import lru_cache, cache
@lru_cache(maxsize=256)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# Python 3.9+ unbounded cache
@cache
def load_config(path: str) -> dict:
with open(path) as f:
return json.load(f)
```
---
### 5. Context Managers
#### Basic @contextmanager
```python
from contextlib import contextmanager
@contextmanager
def managed_connection(dsn: str):
conn = connect(dsn)
try:
yield conn
finally:
conn.close()
with managed_connection("postgres://...") as conn:
conn.execute("SELECT 1")
```
#### Temporary File Context Manager
```python
import tempfile
import os
@contextmanager
def temp_directory():
dirpath = tempfile.mkdtemp()
try:
yield dirpath
finally:
shutil.rmtree(dirpath)
with temp_directory() as tmpdir:
filepath = os.path.join(tmpdir, "data.json")
write_json(filepath, data)
```
#### Lock Context Manager
```python
import threading
@contextmanager
def timed_lock(lock: threading.Lock, timeout: float = 5.0):
acquired = lock.acquire(timeout=timeout)
if not acquired:
raise TimeoutError("Could not acquire lock")
try:
yield
finally:
lock.release()
```
#### Async Context Manager
```python
from contextlib import asynccontextmanager
@asynccontextmanager
async def http_session():
session = aiohttp.ClientSession()
try:
yield session
finally:
await session.close()
```
---
### 6. Pattern Matching
#### Basic match/case
```python
def handle_command(command: str) -> str:
match command.split():
case ["quit"]:
return "Goodbye"
case ["hello", name]:
return f"Hello, {name}"
case ["add", *items]:
return f"Adding {len(items)} items"
case _:
return "Unknown command"
```
#### Structural Patterns
```python
def process_event(event: dict) -> None:
match event:
case {"type": "click", "x": int(x), "y": int(y)}:
handle_click(x, y)
case {"type": "keypress", "key": str(key)} if len(key) == 1:
handle_keypress(key)
case {"type": "resize", "width": w, "height": h}:
handle_resize(w, h)
```
#### Guard Clauses and OR Patterns
```python
def classify_status(code: int) -> str:
match code:
case 200 | 201 | 204:
return "success"
case code if 300 <= code < 400:
return "redirect"
case 401 | 403:
return "auth_error"
case code if 400 <= code < 500:
return "client_error"
case code if 500 <= code < 600:
return "server_error"
case _:
return "unknown"
```
---
### 7. Error Handling
#### Custom Exception Hierarchies
```python
class AppError(Exception):
"""Base exception for the application."""
def __init__(self, message: str, code: str | None = None):
super().__init__(message)
self.code = code
class NotFoundError(AppError):
"""Resource was not found."""
def __init__(self, resource: str, resource_id: str):
super().__init__(f"{resource} {resource_id} not found", code="NOT_FOUND")
self.resource = resource
self.resource_id = resource_id
class ValidationError(AppError):
"""Input validation failed."""
def __init__(self, errors: list[str]):
super().__init__(f"Validation failed: {'; '.join(errors)}", code="VALIDATION")
self.errors = errors
```
#### ExceptionGroup (Python 3.11+)
```python
async def process_batch(items: list[dict]) -> list[dict]:
results = []
errors = []
for item in items:
try:
results.append(await process(item))
except Exception as e:
errors.append(e)
if errors:
raise ExceptionGroup("Batch processing errors", errors)
return results
# Handling with except*
try:
await process_batch(items)
except* ValueError as eg:
print(f"Validation errors: {len(eg.exceptions)}")
except* ConnectionError as eg:
print(f"Connection errors: {len(eg.exceptions)}")
```
#### Exception Chaining
```python
def load_config(path: str) -> dict:
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError as e:
raise AppError(f"Config file missing: {path}") from e
except json.JSONDecodeError as e:
raise AppError(f"Invalid JSON in {path}") from e
```
#### contextlib.suppress
```python
from contextlib import suppress
# Instead of try/except/pass
with suppress(FileNotFoundError):
os.remove("temp_file.txt")
# Instead of:
# try:
# os.remove("temp_file.txt")
# except FileNotFoundError:
# pass
```
---
## Best Practices
1. Use type hints for all public functions
2. Use dataclasses or Pydantic for data models
3. Prefer context managers for resource management
4. Use async for I/O-bound operations
5. Follow PEP 8 style guidelines
1. **Use type hints on all public functions** -- they serve as documentation, enable IDE autocompletion, and allow static analysis with mypy or pyright.
2. **Prefer dataclasses or Pydantic for structured data** -- avoid passing raw dicts around. Use `@dataclass` for internal data, Pydantic `BaseModel` for external boundaries (API input/output, config files).
3. **Use context managers for resource management** -- database connections, file handles, locks, and temporary resources should always be wrapped in `with` statements to guarantee cleanup.
4. **Prefer `asyncio.TaskGroup` over bare `gather`** -- TaskGroup (3.11+) provides proper error handling by cancelling sibling tasks when one fails, avoiding orphaned coroutines.
5. **Follow PEP 8 and use a formatter** -- use `ruff format` or `black` for consistent formatting, and `ruff check` for linting. Configure in `pyproject.toml`.
6. **Write small, composable functions** -- each function should do one thing. Prefer returning values over mutating state. Limit functions to ~20 lines when practical.
7. **Use `__all__` in public modules** -- explicitly declare the public API of a module to prevent accidental imports of internal helpers.
8. **Use `pathlib.Path` over `os.path`** -- pathlib provides a cleaner, object-oriented API for file system operations and works cross-platform.
---
## Common Pitfalls
- **Mutable default arguments**: Use `None` and initialize in function
- **Not closing resources**: Use `with` statements
- **Blocking in async**: Use `asyncio.to_thread()` for CPU work
- **Catching bare exceptions**: Be specific with exception types
1. **Mutable default arguments** -- default values are shared across calls. Use `None` and initialize inside the function body.
```python
# BAD
def add_item(item: str, items: list[str] = []) -> list[str]: ...
# GOOD
def add_item(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items
```
2. **Blocking calls inside async functions** -- calling `time.sleep()`, `requests.get()`, or CPU-heavy code in an async function blocks the entire event loop. Use `asyncio.to_thread()` or `asyncio.sleep()`.
```python
# BAD
async def fetch():
return requests.get(url) # blocks event loop
# GOOD
async def fetch():
return await asyncio.to_thread(requests.get, url)
```
3. **Catching bare `Exception`** -- always be specific about which exceptions you catch. Bare `except:` or `except Exception:` hides bugs.
```python
# BAD
try:
result = compute()
except Exception:
pass
# GOOD
try:
result = compute()
except (ValueError, TypeError) as e:
logger.warning("Computation failed: %s", e)
result = default_value
```
4. **Using `is` for value comparison** -- `is` checks identity, not equality. Only use `is` for `None`, `True`, `False`, and sentinel objects.
```python
# BAD
if x is 42: ...
# GOOD
if x == 42: ...
if x is None: ...
```
5. **Forgetting to close resources** -- file handles, database connections, and HTTP sessions leak if not closed. Always use context managers.
```python
# BAD
f = open("data.txt")
data = f.read()
# GOOD
with open("data.txt") as f:
data = f.read()
```
6. **Circular imports** -- restructure code to avoid circular dependencies. Move shared types into a separate module, use `TYPE_CHECKING` for type-only imports, or use lazy imports.
```python
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User # only imported during type checking
```
---
## Related Skills
- `languages/typescript` -- TypeScript language patterns for polyglot projects
- `frameworks/fastapi` -- FastAPI web framework built on Python
- `frameworks/django` -- Django web framework for Python
- `testing/pytest` -- Python testing with pytest
- `patterns/error-handling` -- Python error handling and exception hierarchies
@@ -0,0 +1,216 @@
# Python Type Hints Quick Reference
> Python 3.10+ syntax preferred. For 3.9, use `from __future__ import annotations`.
## Basic Types
| Type | Example | Notes |
|------|---------|-------|
| `int` | `x: int = 1` | |
| `float` | `x: float = 1.0` | |
| `str` | `x: str = "hi"` | |
| `bool` | `x: bool = True` | |
| `bytes` | `x: bytes = b"hi"` | |
| `None` | `x: None = None` | Use as return type for side-effect functions |
| `object` | `x: object` | Accepts anything, but no attribute access |
| `Any` | `x: Any` | Escapes type checking entirely |
## Collection Types (3.10+)
| Type | Example | Notes |
|------|---------|-------|
| `list[int]` | `x: list[int] = [1, 2]` | Mutable sequence |
| `tuple[int, str]` | `x: tuple[int, str]` | Fixed length |
| `tuple[int, ...]` | `x: tuple[int, ...]` | Variable length |
| `dict[str, int]` | `x: dict[str, int]` | |
| `set[str]` | `x: set[str]` | |
| `frozenset[str]` | `x: frozenset[str]` | |
## Union and Optional
```python
# 3.10+ syntax
def f(x: int | str) -> None: ...
def g(x: int | None = None) -> None: ...
# Pre-3.10
from typing import Union, Optional
def f(x: Union[int, str]) -> None: ...
def g(x: Optional[int] = None) -> None: ...
```
## TypeAlias
```python
from typing import TypeAlias
# Explicit alias (3.10+)
Vector: TypeAlias = list[float]
# 3.12+ syntax
type Vector = list[float]
type Tree[T] = T | list["Tree[T]"] # recursive
```
## Generics with TypeVar
```python
from typing import TypeVar
T = TypeVar("T")
K = TypeVar("K", bound=str) # upper bound
N = TypeVar("N", int, float) # constrained
def first(items: list[T]) -> T:
return items[0]
# 3.12+ syntax (no TypeVar needed)
def first[T](items: list[T]) -> T:
return items[0]
```
## ParamSpec and Concatenate
```python
from typing import ParamSpec, Concatenate, Callable
P = ParamSpec("P")
T = TypeVar("T")
# Preserve function signatures through decorators
def logged(fn: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
# Add a parameter to a function signature
def with_user(
fn: Callable[Concatenate[User, P], T]
) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
return fn(get_current_user(), *args, **kwargs)
return wrapper
```
## Protocol (Structural Typing)
```python
from typing import Protocol, runtime_checkable
class Renderable(Protocol):
def render(self) -> str: ...
class Widget(Protocol):
name: str
def resize(self, width: int, height: int) -> None: ...
# Any class with matching methods satisfies the protocol
class Button:
def render(self) -> str:
return "<button/>"
def draw(item: Renderable) -> None: # Button works here
print(item.render())
# Runtime checking
@runtime_checkable
class Sized(Protocol):
def __len__(self) -> int: ...
assert isinstance([1, 2], Sized) # True at runtime
```
## @overload
```python
from typing import overload
@overload
def parse(data: str) -> dict[str, Any]: ...
@overload
def parse(data: bytes) -> list[int]: ...
@overload
def parse(data: str, raw: Literal[True]) -> str: ...
def parse(data: str | bytes, raw: bool = False) -> dict | list | str:
"""Implementation handles all overloads."""
...
```
## TypeGuard and TypeIs
```python
from typing import TypeGuard, TypeIs # TypeIs: 3.13+
# TypeGuard: narrows type in True branch only
def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
# TypeIs: narrows in both True and False branches
def is_int(val: int | str) -> TypeIs[int]:
return isinstance(val, int)
def f(val: int | str) -> None:
if is_int(val):
reveal_type(val) # int
else:
reveal_type(val) # str (only with TypeIs)
```
## Literal and Final
```python
from typing import Literal, Final
def set_mode(mode: Literal["read", "write", "append"]) -> None: ...
MAX_SIZE: Final = 100 # Cannot be reassigned
PREFIX: Final[str] = "app_" # With explicit type
```
## Callable
| Signature | Meaning |
|-----------|---------|
| `Callable[[int, str], bool]` | Function taking int and str, returning bool |
| `Callable[..., bool]` | Any args, returning bool |
| `Callable[P, T]` | Generic (use with ParamSpec) |
## TypedDict
```python
from typing import TypedDict, NotRequired, Required
class Config(TypedDict):
name: str
debug: NotRequired[bool] # optional key
class PartialConfig(TypedDict, total=False):
name: Required[str] # required even though total=False
debug: bool # optional
```
## Common Patterns
| Pattern | Type Hint |
|---------|-----------|
| JSON value | `dict[str, Any]` or custom TypedDict |
| Decorator preserving sig | `Callable[P, T] -> Callable[P, T]` |
| Context manager | `AbstractContextManager[T]` |
| Async context manager | `AbstractAsyncContextManager[T]` |
| Generator | `Generator[YieldType, SendType, ReturnType]` |
| Async generator | `AsyncGenerator[YieldType, SendType]` |
| Class method returning self | `-> Self` (3.11+, `from typing import Self`) |
| Numeric tower | `int | float` (avoid `numbers.Number`) |
## Type Narrowing Cheat Sheet
| Technique | Example |
|-----------|---------|
| `isinstance` | `if isinstance(x, str):` |
| `is None` / `is not None` | `if x is not None:` |
| `TypeGuard` / `TypeIs` | Custom narrowing functions |
| `assert` | `assert isinstance(x, str)` |
| `Literal` checks | `if x == "read":` |
| `hasattr` | `if hasattr(x, "render"):` (limited) |
+625 -60
View File
@@ -1,9 +1,11 @@
---
name: typescript
description: >
Trigger this skill whenever working with TypeScript files (.ts, .tsx), configuring tsconfig.json, or using TypeScript-specific features like strict typing, generics, utility types, or type guards. Activate for any TypeScript project setup, type definition authoring, Zod schema validation, or discriminated union patterns. Also use when the user asks about avoiding `any`, enabling strict mode, or migrating JavaScript to TypeScript.
---
# TypeScript
## Description
TypeScript development with strict typing, advanced type utilities, and modern patterns.
## When to Use
- Working with TypeScript files (.ts, .tsx)
@@ -11,50 +13,284 @@ TypeScript development with strict typing, advanced type utilities, and modern p
- React/Next.js development
- Node.js backend development
## When NOT to Use
- Pure Python projects with no TypeScript components
- JavaScript projects that have no TypeScript setup and are not being migrated to TypeScript
---
## Core Patterns
### Type Definitions
### 1. Advanced Types
#### Discriminated Unions
```typescript
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
}
}
```
#### Branded Types
```typescript
type UserId = string & { readonly __brand: unique symbol };
type OrderId = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
if (!id.startsWith("usr_")) throw new Error("Invalid user ID");
return id as UserId;
}
function getUser(id: UserId): User { ... }
// Prevents mixing IDs:
const userId = createUserId("usr_123");
const orderId = "ord_456" as OrderId;
// getUser(orderId); // compile error
```
#### Template Literal Types
```typescript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = `/api/${string}`;
type EventName = `on${Capitalize<string>}`;
// Combine for precise route definitions
type Endpoint = `${Uppercase<HttpMethod>} ${ApiRoute}`;
// Pattern matching on string types
type ExtractParam<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParam<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParam<"/users/:userId/posts/:postId">;
// Result: "userId" | "postId"
```
#### Conditional Types with infer
```typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type UnwrapArray<T> = T extends (infer U)[] ? U : T;
// Deeply unwrap nested promises
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
// Extract function return type conditionally
type AsyncReturnType<T extends (...args: any[]) => any> =
ReturnType<T> extends Promise<infer U> ? U : ReturnType<T>;
```
#### Mapped Types
```typescript
// Make all properties optional and nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Create a readonly version with getters
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// Remove specific keys
type RemoveKind<T> = {
[K in keyof T as Exclude<K, "kind">]: T[K];
};
```
#### Recursive Types
```typescript
type Json =
| string
| number
| boolean
| null
| Json[]
| { [key: string]: Json };
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
```
---
### 2. Utility Types
```typescript
// Interfaces for objects
interface User {
id: string;
email: string;
name: string;
role: "admin" | "user" | "guest";
createdAt: Date;
}
// Types for unions and utilities
type Status = 'pending' | 'active' | 'inactive';
type UserWithStatus = User & { status: Status };
// Generic types
type ApiResponse<T> = {
data: T;
error?: string;
status: number;
};
```
### Utility Types
```typescript
// Partial - all properties optional
// Partial -- all properties optional (useful for update payloads)
type UserUpdate = Partial<User>;
// Pick - select properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Required -- all properties required (undo optionality)
type CompleteUser = Required<User>;
// Omit - exclude properties
type UserWithoutId = Omit<User, 'id'>;
// Pick -- select specific properties
type UserPreview = Pick<User, "id" | "name">;
// Record - dictionary type
type UserMap = Record<string, User>;
// Omit -- exclude specific properties
type UserCreate = Omit<User, "id" | "createdAt">;
// Record -- dictionary with typed keys and values
type RolePermissions = Record<User["role"], string[]>;
// Exclude -- remove members from a union
type NonGuestRole = Exclude<User["role"], "guest">;
// Result: "admin" | "user"
// Extract -- keep only matching members from a union
type PrivilegedRole = Extract<User["role"], "admin" | "moderator">;
// Result: "admin"
// ReturnType -- extract return type of a function
declare function getUser(): Promise<User>;
type GetUserResult = ReturnType<typeof getUser>;
// Result: Promise<User>
// Parameters -- extract parameter types as a tuple
type GetUserParams = Parameters<typeof getUser>;
// Awaited -- unwrap Promise types
type ResolvedUser = Awaited<ReturnType<typeof getUser>>;
// Result: User
// NonNullable -- remove null and undefined
type DefinitelyString = NonNullable<string | null | undefined>;
// Result: string
```
### Async Patterns
---
### 3. Generics
#### Generic Functions
```typescript
function first<T>(items: T[]): T | undefined {
return items[0];
}
function groupBy<T, K extends string | number>(
items: T[],
keyFn: (item: T) => K,
): Record<K, T[]> {
const result = {} as Record<K, T[]>;
for (const item of items) {
const key = keyFn(item);
(result[key] ??= []).push(item);
}
return result;
}
```
#### Generic Constraints with extends
```typescript
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id);
}
// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
```
#### Generic Classes
```typescript
class Repository<T extends HasId> {
private items = new Map<string, T>();
save(item: T): void {
this.items.set(item.id, item);
}
findById(id: string): T | undefined {
return this.items.get(id);
}
findAll(): T[] {
return [...this.items.values()];
}
}
const userRepo = new Repository<User>();
```
#### Default Type Parameters
```typescript
type ApiResponse<T, E = Error> = {
data: T | null;
error: E | null;
status: number;
};
// Uses default Error type
const response: ApiResponse<User> = {
data: user,
error: null,
status: 200,
};
// Override with custom error
const response2: ApiResponse<User, ValidationError> = { ... };
```
#### const Type Parameters (TypeScript 5.0+)
```typescript
function createRoute<const T extends readonly string[]>(
methods: T,
path: string,
) {
return { methods, path };
}
// Infers literal tuple type ["GET", "POST"] instead of string[]
const route = createRoute(["GET", "POST"], "/api/users");
```
---
### 4. Async Patterns
#### Promise Typing
```typescript
async function fetchUser(id: string): Promise<User> {
@@ -62,67 +298,396 @@ async function fetchUser(id: string): Promise<User> {
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
return response.json() as Promise<User>;
}
```
// Error handling
async function safeOperation<T>(
operation: () => Promise<T>
): Promise<[T, null] | [null, Error]> {
#### Promise.all with Tuple Types
```typescript
async function loadDashboard(userId: string) {
const [user, posts, notifications] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchNotifications(userId),
] as const);
// user: User, posts: Post[], notifications: Notification[]
return { user, posts, notifications };
}
```
#### Result Pattern for Error Handling
```typescript
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function safeAsync<T>(
fn: () => Promise<T>,
): Promise<Result<T>> {
try {
const result = await operation();
return [result, null];
return { ok: true, value: await fn() };
} catch (error) {
return [null, error as Error];
return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
}
}
const result = await safeAsync(() => fetchUser("123"));
if (result.ok) {
console.log(result.value.name);
} else {
console.error(result.error.message);
}
```
### Class Patterns
#### AbortController Patterns
```typescript
class UserService {
constructor(private readonly db: Database) {}
async function fetchWithTimeout(
url: string,
timeoutMs: number = 5000,
): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
async findById(id: string): Promise<User | null> {
return this.db.users.findUnique({ where: { id } });
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeoutId);
}
}
async create(data: UserCreate): Promise<User> {
return this.db.users.create({ data });
}
// Cancellable operation
function createCancellableRequest(url: string) {
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal });
return {
promise,
cancel: () => controller.abort(),
};
}
```
### Zod Validation
---
### 5. Zod Integration
#### Schema Definition and Inference
```typescript
import { z } from 'zod';
import { z } from "zod";
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
password: z.string().min(8),
age: z.number().int().min(0).max(150),
role: z.enum(["admin", "user", "guest"]),
});
type UserInput = z.infer<typeof UserSchema>;
type User = z.infer<typeof UserSchema>;
```
function validateUser(data: unknown): UserInput {
return UserSchema.parse(data);
#### Refinements and Transforms
```typescript
const PasswordSchema = z
.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), "Must contain uppercase")
.refine((val) => /[0-9]/.test(val), "Must contain number");
const DateStringSchema = z
.string()
.transform((val) => new Date(val))
.refine((date) => !isNaN(date.getTime()), "Invalid date");
const MoneySchema = z
.string()
.transform((val) => parseFloat(val.replace(/[$,]/g, "")))
.pipe(z.number().positive());
```
#### Discriminated Unions with Zod
```typescript
const ShapeSchema = z.discriminatedUnion("kind", [
z.object({ kind: z.literal("circle"), radius: z.number().positive() }),
z.object({ kind: z.literal("rectangle"), width: z.number(), height: z.number() }),
]);
type Shape = z.infer<typeof ShapeSchema>;
function validateShape(input: unknown): Shape {
return ShapeSchema.parse(input);
}
```
#### Zod with API Validation
```typescript
const QueryParamsSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
search: z.string().optional(),
sort: z.enum(["name", "date", "relevance"]).default("date"),
});
type QueryParams = z.infer<typeof QueryParamsSchema>;
function parseQuery(raw: Record<string, string>): QueryParams {
return QueryParamsSchema.parse(raw);
}
```
---
### 6. Module Patterns
#### Barrel Exports
```typescript
// src/models/index.ts
export { User, type UserCreate } from "./user.js";
export { Post, type PostCreate } from "./post.js";
export { Comment } from "./comment.js";
```
#### Declaration Merging
```typescript
// Extend an existing interface
interface Window {
analytics: AnalyticsClient;
}
// Extend Express Request
declare namespace Express {
interface Request {
user?: AuthenticatedUser;
}
}
```
#### Module Augmentation
```typescript
// Augment a third-party module
import "express";
declare module "express" {
interface Request {
requestId: string;
startTime: number;
}
}
```
#### Ambient Declarations (.d.ts)
```typescript
// global.d.ts
declare global {
interface ImportMeta {
env: {
VITE_API_URL: string;
VITE_APP_TITLE: string;
};
}
}
// Declare untyped modules
declare module "legacy-lib" {
export function doSomething(input: string): string;
}
export {};
```
---
### 7. Type Guards
#### Built-in Narrowing
```typescript
function process(value: string | number | null) {
if (typeof value === "string") {
// value: string
return value.toUpperCase();
}
if (typeof value === "number") {
// value: number
return value.toFixed(2);
}
// value: null
return "N/A";
}
```
#### instanceof Guard
```typescript
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
) {
super(message);
}
}
function handleError(error: unknown): string {
if (error instanceof ApiError) {
return `API Error ${error.statusCode}: ${error.message}`;
}
if (error instanceof Error) {
return error.message;
}
return String(error);
}
```
#### in Operator Guard
```typescript
interface Dog { bark(): void; breed: string; }
interface Cat { meow(): void; color: string; }
function speak(animal: Dog | Cat): void {
if ("bark" in animal) {
animal.bark(); // animal: Dog
} else {
animal.meow(); // animal: Cat
}
}
```
#### Custom Type Predicates (is)
```typescript
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"email" in value &&
typeof (value as User).id === "string"
);
}
function processInput(data: unknown) {
if (isUser(data)) {
// data: User -- fully narrowed
console.log(data.email);
}
}
```
#### Assertion Functions (asserts)
```typescript
function assertDefined<T>(
value: T | null | undefined,
message?: string,
): asserts value is T {
if (value === null || value === undefined) {
throw new Error(message ?? "Value is null or undefined");
}
}
function processUser(maybeUser: User | null): string {
assertDefined(maybeUser, "User is required");
// maybeUser: User after this point
return maybeUser.name;
}
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
// Exhaustiveness checking in switch
function getLabel(role: "admin" | "user" | "guest"): string {
switch (role) {
case "admin": return "Administrator";
case "user": return "Standard User";
case "guest": return "Guest";
default: return assertNever(role); // compile error if a case is missed
}
}
```
---
## Best Practices
1. Enable strict mode in tsconfig.json
2. Avoid `any` - use `unknown` and type guards
3. Use interfaces for object shapes, types for unions
4. Prefer `const` assertions for literal types
5. Use discriminated unions for state
1. **Enable strict mode in tsconfig.json** -- set `"strict": true` which enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, and other safety checks.
2. **Never use `any` -- use `unknown` instead** -- when the type is truly unknown, use `unknown` and narrow with type guards. Reserve `any` only for exceptional migration scenarios, and flag it with `// eslint-disable-next-line`.
3. **Use interfaces for object shapes, types for unions** -- interfaces support declaration merging and produce clearer error messages. Types are better for unions, intersections, and mapped types.
4. **Prefer discriminated unions for state modeling** -- use a shared literal `kind` or `type` field to enable exhaustive switch statements and precise narrowing.
5. **Use `as const` for literal inference** -- `const assertions` preserve literal types in arrays and objects, avoiding unwanted widening to `string[]` or `number[]`.
6. **Validate external data at boundaries** -- use Zod or a similar runtime validator at API boundaries, config loading, and form inputs. Never trust `as` casts for unknown data.
7. **Prefer type predicates over type assertions** -- custom `is` guards are safer than `as` casts because they include a runtime check.
8. **Use `satisfies` for type checking without widening** -- the `satisfies` operator (TS 5.0+) validates that a value conforms to a type while preserving the narrower inferred type.
```typescript
const config = {
apiUrl: "https://api.example.com",
retries: 3,
} satisfies Record<string, string | number>;
// config.apiUrl is still string (not string | number)
```
---
## Common Pitfalls
- **Using `any`**: Defeats type safety
- **Not handling null/undefined**: Use strict null checks
- **Type assertions**: Prefer type guards
- **Ignoring errors**: Handle all promise rejections
1. **Overusing type assertions (`as`)** -- assertions bypass the type checker. Use type guards or schema validation instead.
```typescript
// BAD
const user = data as User;
// GOOD
if (isUser(data)) { ... }
```
2. **Ignoring strict null checks** -- `undefined` and `null` cause runtime crashes when not handled. Always enable `strictNullChecks` and handle nullable values explicitly.
3. **Returning `any` from catch blocks** -- `catch (e)` gives `unknown` in strict mode. Always narrow before using the error.
```typescript
catch (error) {
const message = error instanceof Error ? error.message : String(error);
}
```
4. **Mutation of readonly types at runtime** -- `Readonly<T>` and `readonly` only prevent mutation at compile time. The underlying object can still be mutated at runtime via `Object.assign` or casts.
5. **Forgetting `export {}` in ambient files** -- `.d.ts` files without any import/export are treated as global scripts rather than modules, which can cause unexpected declaration collisions.
6. **Using enums instead of const objects** -- TypeScript enums have quirks (reverse mappings, tree-shaking issues). Prefer `as const` objects or union types.
```typescript
// Prefer this:
const Role = { Admin: "admin", User: "user" } as const;
type Role = (typeof Role)[keyof typeof Role];
// Over this:
enum Role { Admin = "admin", User = "user" }
```
---
## Related Skills
- `languages/javascript` -- JavaScript patterns for JS interop and migration
- `languages/python` -- Python language patterns for polyglot projects
- `frameworks/react` -- React component patterns with TypeScript
- `frameworks/nextjs` -- Next.js framework with TypeScript support
- `testing/vitest` -- TypeScript testing with Vitest
- `patterns/error-handling` -- TypeScript error handling patterns
- `patterns/state-management` -- State management with TypeScript types
@@ -0,0 +1,249 @@
# TypeScript Advanced Types Quick Reference
## Discriminated Unions
```typescript
// Tag each variant with a literal type field
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rect": return s.width * s.height;
case "triangle": return (s.base * s.height) / 2;
}
}
// Exhaustiveness check helper
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
```
## Branded Types
```typescript
// Prevent mixing structurally identical types
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function getUser(id: UserId) { /* ... */ }
const userId = "abc" as UserId;
const orderId = "abc" as OrderId;
getUser(userId); // OK
getUser(orderId); // Error: OrderId not assignable to UserId
// Validation-based branding
type Email = Brand<string, "Email">;
function parseEmail(input: string): Email {
if (!input.includes("@")) throw new Error("Invalid email");
return input as Email;
}
```
## Template Literal Types
```typescript
// Build string types from unions
type Method = "get" | "post" | "put" | "delete";
type Route = "/users" | "/orders";
type Endpoint = `${Uppercase<Method>} ${Route}`;
// "GET /users" | "GET /orders" | "POST /users" | ...
// Event handler pattern
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
// Extract parts from string types
type ExtractParam<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParam<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParam<"/users/:id/posts/:postId">;
// "id" | "postId"
```
## Conditional Types
```typescript
// Basic conditional
type IsString<T> = T extends string ? true : false;
// Distributive over unions (when T is naked type parameter)
type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// Prevent distribution with tuple wrapping
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>; // (string | number)[]
// infer keyword
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;
type Unpacked<T> = T extends Promise<infer U> ? U :
T extends Array<infer U> ? U : T;
// infer with constraints (TS 4.7+)
type FirstString<T> =
T extends [infer S extends string, ...unknown[]] ? S : never;
```
## Mapped Types
```typescript
// Transform all properties
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Optional<T> = { [K in keyof T]?: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
// Map to new value types
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 }
```
## Key Remapping (via `as`)
```typescript
// Filter keys
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
// Rename keys
type Prefixed<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
// Remove specific keys
type RemoveKind<T> = {
[K in keyof T as Exclude<K, "kind">]: T[K];
};
// Build from union
type EventMap<T extends string> = {
[K in T as `on${Capitalize<K>}`]: (event: K) => void;
};
type Handlers = EventMap<"click" | "scroll">;
// { onClick: (event: "click") => void; onScroll: (event: "scroll") => void }
```
## Recursive Types
```typescript
// JSON type
type Json =
| string
| number
| boolean
| null
| Json[]
| { [key: string]: Json };
// Deep readonly
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
// Deep partial
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// Flatten nested object paths
type Paths<T, Prefix extends string = ""> = T extends object
? {
[K in keyof T & string]: T[K] extends object
? Paths<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;
type P = Paths<{ a: { b: number; c: { d: string } } }>;
// "a.b" | "a.c.d"
```
## Utility Types Cheat Sheet
| Utility | Effect |
|---------|--------|
| `Partial<T>` | All properties optional |
| `Required<T>` | All properties required |
| `Readonly<T>` | All properties readonly |
| `Record<K, V>` | Object with keys K and values V |
| `Pick<T, K>` | Subset of properties |
| `Omit<T, K>` | All except listed properties |
| `Exclude<U, E>` | Remove members from union |
| `Extract<U, E>` | Keep matching members from union |
| `NonNullable<T>` | Remove null and undefined |
| `ReturnType<F>` | Return type of function |
| `Parameters<F>` | Tuple of parameter types |
| `ConstructorParameters<C>` | Constructor parameter types |
| `InstanceType<C>` | Instance type of constructor |
| `Awaited<T>` | Unwrap Promise (deeply) |
| `NoInfer<T>` | Prevent inference from this position (5.4+) |
## Satisfies Operator (4.9+)
```typescript
// Validate type without widening
const palette = {
red: "#ff0000",
green: [0, 255, 0],
} satisfies Record<string, string | number[]>;
palette.red.toUpperCase(); // OK - knows it's string
palette.green.map(x => x); // OK - knows it's number[]
```
## const Type Parameters (5.0+)
```typescript
// Infer narrow literal types from arguments
function routes<const T extends readonly string[]>(paths: T): T {
return paths;
}
const r = routes(["/home", "/about"]);
// Type: readonly ["/home", "/about"] (not string[])
```
## Pattern: Type-Safe Event Emitter
```typescript
type EventMap = {
login: { userId: string };
logout: undefined;
error: { code: number; message: string };
};
class Emitter<E extends Record<string, unknown>> {
on<K extends keyof E>(
event: K,
handler: E[K] extends undefined
? () => void
: (payload: E[K]) => void
): void { /* ... */ }
emit<K extends keyof E>(
...args: E[K] extends undefined ? [K] : [K, E[K]]
): void { /* ... */ }
}
```