style: format all files with prettier

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

View File

@@ -23,11 +23,13 @@ Build secure, scalable authentication and authorization systems using industry-s
### 1. Authentication vs Authorization
**Authentication (AuthN)**: Who are you?
- Verifying identity (username/password, OAuth, biometrics)
- Issuing credentials (sessions, tokens)
- Managing login/logout
**Authorization (AuthZ)**: What can you do?
- Permission checking
- Role-based access control (RBAC)
- Resource ownership validation
@@ -36,16 +38,19 @@ Build secure, scalable authentication and authorization systems using industry-s
### 2. Authentication Strategies
**Session-Based:**
- Server stores session state
- Session ID in cookie
- Traditional, simple, stateful
**Token-Based (JWT):**
- Stateless, self-contained
- Scales horizontally
- Can store claims
**OAuth2/OpenID Connect:**
- Delegate authentication
- Social login (Google, GitHub)
- Enterprise SSO
@@ -56,69 +61,69 @@ Build secure, scalable authentication and authorization systems using industry-s
```typescript
// JWT structure: header.payload.signature
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";
interface JWTPayload {
userId: string;
email: string;
role: string;
iat: number;
exp: number;
userId: string;
email: string;
role: string;
iat: number;
exp: number;
}
// Generate JWT
function generateTokens(userId: string, email: string, role: string) {
const accessToken = jwt.sign(
{ userId, email, role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // Short-lived
);
const accessToken = jwt.sign(
{ userId, email, role },
process.env.JWT_SECRET!,
{ expiresIn: "15m" }, // Short-lived
);
const refreshToken = jwt.sign(
{ userId },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' } // Long-lived
);
const refreshToken = jwt.sign(
{ userId },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: "7d" }, // Long-lived
);
return { accessToken, refreshToken };
return { accessToken, refreshToken };
}
// Verify JWT
function verifyToken(token: string): JWTPayload {
try {
return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error('Token expired');
}
if (error instanceof jwt.JsonWebTokenError) {
throw new Error('Invalid token');
}
throw error;
try {
return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error("Token expired");
}
if (error instanceof jwt.JsonWebTokenError) {
throw new Error("Invalid token");
}
throw error;
}
}
// Middleware
function authenticate(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "No token provided" });
}
const token = authHeader.substring(7);
try {
const payload = verifyToken(token);
req.user = payload; // Attach user to request
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
const token = authHeader.substring(7);
try {
const payload = verifyToken(token);
req.user = payload; // Attach user to request
next();
} catch (error) {
return res.status(401).json({ error: "Invalid token" });
}
}
// Usage
app.get('/api/profile', authenticate, (req, res) => {
res.json({ user: req.user });
app.get("/api/profile", authenticate, (req, res) => {
res.json({ user: req.user });
});
```
@@ -126,94 +131,93 @@ app.get('/api/profile', authenticate, (req, res) => {
```typescript
interface StoredRefreshToken {
token: string;
userId: string;
expiresAt: Date;
createdAt: Date;
token: string;
userId: string;
expiresAt: Date;
createdAt: Date;
}
class RefreshTokenService {
// Store refresh token in database
async storeRefreshToken(userId: string, refreshToken: string) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
await db.refreshTokens.create({
token: await hash(refreshToken), // Hash before storing
userId,
expiresAt,
});
// Store refresh token in database
async storeRefreshToken(userId: string, refreshToken: string) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
await db.refreshTokens.create({
token: await hash(refreshToken), // Hash before storing
userId,
expiresAt,
});
}
// Refresh access token
async refreshAccessToken(refreshToken: string) {
// Verify refresh token
let payload;
try {
payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as {
userId: string;
};
} catch {
throw new Error("Invalid refresh token");
}
// Refresh access token
async refreshAccessToken(refreshToken: string) {
// Verify refresh token
let payload;
try {
payload = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_SECRET!
) as { userId: string };
} catch {
throw new Error('Invalid refresh token');
}
// Check if token exists in database
const storedToken = await db.refreshTokens.findOne({
where: {
token: await hash(refreshToken),
userId: payload.userId,
expiresAt: { $gt: new Date() },
},
});
// Check if token exists in database
const storedToken = await db.refreshTokens.findOne({
where: {
token: await hash(refreshToken),
userId: payload.userId,
expiresAt: { $gt: new Date() },
},
});
if (!storedToken) {
throw new Error('Refresh token not found or expired');
}
// Get user
const user = await db.users.findById(payload.userId);
if (!user) {
throw new Error('User not found');
}
// Generate new access token
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
return { accessToken };
if (!storedToken) {
throw new Error("Refresh token not found or expired");
}
// Revoke refresh token (logout)
async revokeRefreshToken(refreshToken: string) {
await db.refreshTokens.deleteOne({
token: await hash(refreshToken),
});
// Get user
const user = await db.users.findById(payload.userId);
if (!user) {
throw new Error("User not found");
}
// Revoke all user tokens (logout all devices)
async revokeAllUserTokens(userId: string) {
await db.refreshTokens.deleteMany({ userId });
}
// Generate new access token
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: "15m" },
);
return { accessToken };
}
// Revoke refresh token (logout)
async revokeRefreshToken(refreshToken: string) {
await db.refreshTokens.deleteOne({
token: await hash(refreshToken),
});
}
// Revoke all user tokens (logout all devices)
async revokeAllUserTokens(userId: string) {
await db.refreshTokens.deleteMany({ userId });
}
}
// API endpoints
app.post('/api/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
try {
const { accessToken } = await refreshTokenService
.refreshAccessToken(refreshToken);
res.json({ accessToken });
} catch (error) {
res.status(401).json({ error: 'Invalid refresh token' });
}
app.post("/api/auth/refresh", async (req, res) => {
const { refreshToken } = req.body;
try {
const { accessToken } =
await refreshTokenService.refreshAccessToken(refreshToken);
res.json({ accessToken });
} catch (error) {
res.status(401).json({ error: "Invalid refresh token" });
}
});
app.post('/api/auth/logout', authenticate, async (req, res) => {
const { refreshToken } = req.body;
await refreshTokenService.revokeRefreshToken(refreshToken);
res.json({ message: 'Logged out successfully' });
app.post("/api/auth/logout", authenticate, async (req, res) => {
const { refreshToken } = req.body;
await refreshTokenService.revokeRefreshToken(refreshToken);
res.json({ message: "Logged out successfully" });
});
```
@@ -222,70 +226,70 @@ app.post('/api/auth/logout', authenticate, async (req, res) => {
### Pattern 1: Express Session
```typescript
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";
// Setup Redis for session storage
const redisClient = createClient({
url: process.env.REDIS_URL,
url: process.env.REDIS_URL,
});
await redisClient.connect();
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only
httpOnly: true, // No JavaScript access
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: 'strict', // CSRF protection
},
})
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production", // HTTPS only
httpOnly: true, // No JavaScript access
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: "strict", // CSRF protection
},
}),
);
// Login
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
app.post("/api/auth/login", async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findOne({ email });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const user = await db.users.findOne({ email });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return res.status(401).json({ error: "Invalid credentials" });
}
// Store user in session
req.session.userId = user.id;
req.session.role = user.role;
// Store user in session
req.session.userId = user.id;
req.session.role = user.role;
res.json({ user: { id: user.id, email: user.email, role: user.role } });
res.json({ user: { id: user.id, email: user.email, role: user.role } });
});
// Session middleware
function requireAuth(req: Request, res: Response, next: NextFunction) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
next();
if (!req.session.userId) {
return res.status(401).json({ error: "Not authenticated" });
}
next();
}
// Protected route
app.get('/api/profile', requireAuth, async (req, res) => {
const user = await db.users.findById(req.session.userId);
res.json({ user });
app.get("/api/profile", requireAuth, async (req, res) => {
const user = await db.users.findById(req.session.userId);
res.json({ user });
});
// Logout
app.post('/api/auth/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('connect.sid');
res.json({ message: 'Logged out successfully' });
});
app.post("/api/auth/logout", (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: "Logout failed" });
}
res.clearCookie("connect.sid");
res.json({ message: "Logged out successfully" });
});
});
```
@@ -294,56 +298,61 @@ app.post('/api/auth/logout', (req, res) => {
### Pattern 1: OAuth2 with Passport.js
```typescript
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as GitHubStrategy } from "passport-github2";
// Google OAuth
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: '/api/auth/google/callback',
},
async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user
let user = await db.users.findOne({
googleId: profile.id,
});
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: "/api/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user
let user = await db.users.findOne({
googleId: profile.id,
});
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName,
avatar: profile.photos?.[0]?.value,
});
}
return done(null, user);
} catch (error) {
return done(error, undefined);
}
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName,
avatar: profile.photos?.[0]?.value,
});
}
)
return done(null, user);
} catch (error) {
return done(error, undefined);
}
},
),
);
// Routes
app.get('/api/auth/google', passport.authenticate('google', {
scope: ['profile', 'email'],
}));
app.get(
"/api/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"],
}),
);
app.get(
'/api/auth/google/callback',
passport.authenticate('google', { session: false }),
(req, res) => {
// Generate JWT
const tokens = generateTokens(req.user.id, req.user.email, req.user.role);
// Redirect to frontend with token
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`);
}
"/api/auth/google/callback",
passport.authenticate("google", { session: false }),
(req, res) => {
// Generate JWT
const tokens = generateTokens(req.user.id, req.user.email, req.user.role);
// Redirect to frontend with token
res.redirect(
`${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`,
);
},
);
```
@@ -353,45 +362,46 @@ app.get(
```typescript
enum Role {
USER = 'user',
MODERATOR = 'moderator',
ADMIN = 'admin',
USER = "user",
MODERATOR = "moderator",
ADMIN = "admin",
}
const roleHierarchy: Record<Role, Role[]> = {
[Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],
[Role.MODERATOR]: [Role.MODERATOR, Role.USER],
[Role.USER]: [Role.USER],
[Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],
[Role.MODERATOR]: [Role.MODERATOR, Role.USER],
[Role.USER]: [Role.USER],
};
function hasRole(userRole: Role, requiredRole: Role): boolean {
return roleHierarchy[userRole].includes(requiredRole);
return roleHierarchy[userRole].includes(requiredRole);
}
// Middleware
function requireRole(...roles: Role[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
if (!roles.some(role => hasRole(req.user.role, role))) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
if (!roles.some((role) => hasRole(req.user.role, role))) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
next();
};
}
// Usage
app.delete('/api/users/:id',
authenticate,
requireRole(Role.ADMIN),
async (req, res) => {
// Only admins can delete users
await db.users.delete(req.params.id);
res.json({ message: 'User deleted' });
}
app.delete(
"/api/users/:id",
authenticate,
requireRole(Role.ADMIN),
async (req, res) => {
// Only admins can delete users
await db.users.delete(req.params.id);
res.json({ message: "User deleted" });
},
);
```
@@ -399,53 +409,54 @@ app.delete('/api/users/:id',
```typescript
enum Permission {
READ_USERS = 'read:users',
WRITE_USERS = 'write:users',
DELETE_USERS = 'delete:users',
READ_POSTS = 'read:posts',
WRITE_POSTS = 'write:posts',
READ_USERS = "read:users",
WRITE_USERS = "write:users",
DELETE_USERS = "delete:users",
READ_POSTS = "read:posts",
WRITE_POSTS = "write:posts",
}
const rolePermissions: Record<Role, Permission[]> = {
[Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],
[Role.MODERATOR]: [
Permission.READ_POSTS,
Permission.WRITE_POSTS,
Permission.READ_USERS,
],
[Role.ADMIN]: Object.values(Permission),
[Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],
[Role.MODERATOR]: [
Permission.READ_POSTS,
Permission.WRITE_POSTS,
Permission.READ_USERS,
],
[Role.ADMIN]: Object.values(Permission),
};
function hasPermission(userRole: Role, permission: Permission): boolean {
return rolePermissions[userRole]?.includes(permission) ?? false;
return rolePermissions[userRole]?.includes(permission) ?? false;
}
function requirePermission(...permissions: Permission[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
const hasAllPermissions = permissions.every(permission =>
hasPermission(req.user.role, permission)
);
const hasAllPermissions = permissions.every((permission) =>
hasPermission(req.user.role, permission),
);
if (!hasAllPermissions) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
if (!hasAllPermissions) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
next();
};
}
// Usage
app.get('/api/users',
authenticate,
requirePermission(Permission.READ_USERS),
async (req, res) => {
const users = await db.users.findAll();
res.json({ users });
}
app.get(
"/api/users",
authenticate,
requirePermission(Permission.READ_USERS),
async (req, res) => {
const users = await db.users.findAll();
res.json({ users });
},
);
```
@@ -454,50 +465,51 @@ app.get('/api/users',
```typescript
// Check if user owns resource
async function requireOwnership(
resourceType: 'post' | 'comment',
resourceIdParam: string = 'id'
resourceType: "post" | "comment",
resourceIdParam: string = "id",
) {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
const resourceId = req.params[resourceIdParam];
const resourceId = req.params[resourceIdParam];
// Admins can access anything
if (req.user.role === Role.ADMIN) {
return next();
}
// Admins can access anything
if (req.user.role === Role.ADMIN) {
return next();
}
// Check ownership
let resource;
if (resourceType === 'post') {
resource = await db.posts.findById(resourceId);
} else if (resourceType === 'comment') {
resource = await db.comments.findById(resourceId);
}
// Check ownership
let resource;
if (resourceType === "post") {
resource = await db.posts.findById(resourceId);
} else if (resourceType === "comment") {
resource = await db.comments.findById(resourceId);
}
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
if (!resource) {
return res.status(404).json({ error: "Resource not found" });
}
if (resource.userId !== req.user.userId) {
return res.status(403).json({ error: 'Not authorized' });
}
if (resource.userId !== req.user.userId) {
return res.status(403).json({ error: "Not authorized" });
}
next();
};
next();
};
}
// Usage
app.put('/api/posts/:id',
authenticate,
requireOwnership('post'),
async (req, res) => {
// User can only update their own posts
const post = await db.posts.update(req.params.id, req.body);
res.json({ post });
}
app.put(
"/api/posts/:id",
authenticate,
requireOwnership("post"),
async (req, res) => {
// User can only update their own posts
const post = await db.posts.update(req.params.id, req.body);
res.json({ post });
},
);
```
@@ -506,99 +518,100 @@ app.put('/api/posts/:id',
### Pattern 1: Password Security
```typescript
import bcrypt from 'bcrypt';
import { z } from 'zod';
import bcrypt from "bcrypt";
import { z } from "zod";
// Password validation schema
const passwordSchema = z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character');
const passwordSchema = z
.string()
.min(12, "Password must be at least 12 characters")
.regex(/[A-Z]/, "Password must contain uppercase letter")
.regex(/[a-z]/, "Password must contain lowercase letter")
.regex(/[0-9]/, "Password must contain number")
.regex(/[^A-Za-z0-9]/, "Password must contain special character");
// Hash password
async function hashPassword(password: string): Promise<string> {
const saltRounds = 12; // 2^12 iterations
return bcrypt.hash(password, saltRounds);
const saltRounds = 12; // 2^12 iterations
return bcrypt.hash(password, saltRounds);
}
// Verify password
async function verifyPassword(
password: string,
hash: string
password: string,
hash: string,
): Promise<boolean> {
return bcrypt.compare(password, hash);
return bcrypt.compare(password, hash);
}
// Registration with password validation
app.post('/api/auth/register', async (req, res) => {
try {
const { email, password } = req.body;
app.post("/api/auth/register", async (req, res) => {
try {
const { email, password } = req.body;
// Validate password
passwordSchema.parse(password);
// Validate password
passwordSchema.parse(password);
// Check if user exists
const existingUser = await db.users.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'Email already registered' });
}
// Hash password
const passwordHash = await hashPassword(password);
// Create user
const user = await db.users.create({
email,
passwordHash,
});
// Generate tokens
const tokens = generateTokens(user.id, user.email, user.role);
res.status(201).json({
user: { id: user.id, email: user.email },
...tokens,
});
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ error: error.errors[0].message });
}
res.status(500).json({ error: 'Registration failed' });
// Check if user exists
const existingUser = await db.users.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: "Email already registered" });
}
// Hash password
const passwordHash = await hashPassword(password);
// Create user
const user = await db.users.create({
email,
passwordHash,
});
// Generate tokens
const tokens = generateTokens(user.id, user.email, user.role);
res.status(201).json({
user: { id: user.id, email: user.email },
...tokens,
});
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ error: error.errors[0].message });
}
res.status(500).json({ error: "Registration failed" });
}
});
```
### Pattern 2: Rate Limiting
```typescript
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
// Login rate limiter
const loginLimiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts, please try again later',
standardHeaders: true,
legacyHeaders: false,
store: new RedisStore({ client: redisClient }),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: "Too many login attempts, please try again later",
standardHeaders: true,
legacyHeaders: false,
});
// API rate limiter
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
standardHeaders: true,
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
standardHeaders: true,
});
// Apply to routes
app.post('/api/auth/login', loginLimiter, async (req, res) => {
// Login logic
app.post("/api/auth/login", loginLimiter, async (req, res) => {
// Login logic
});
app.use('/api/', apiLimiter);
app.use("/api/", apiLimiter);
```
## Best Practices

View File

@@ -39,13 +39,13 @@ workspace/
### 2. Key Concepts
| Concept | Description |
|---------|-------------|
| **Target** | Buildable unit (library, binary, test) |
| **Package** | Directory with BUILD file |
| **Label** | Target identifier `//path/to:target` |
| **Rule** | Defines how to build a target |
| **Aspect** | Cross-cutting build behavior |
| Concept | Description |
| ----------- | -------------------------------------- |
| **Target** | Buildable unit (library, binary, test) |
| **Package** | Directory with BUILD file |
| **Label** | Target identifier `//path/to:target` |
| **Rule** | Defines how to build a target |
| **Aspect** | Cross-cutting build behavior |
## Templates
@@ -366,6 +366,7 @@ bazel build //... --notrack_incremental_state
## Best Practices
### Do's
- **Use fine-grained targets** - Better caching
- **Pin dependencies** - Reproducible builds
- **Enable remote caching** - Share build artifacts
@@ -373,8 +374,9 @@ bazel build //... --notrack_incremental_state
- **Write BUILD files per directory** - Standard convention
### Don'ts
- **Don't use glob for deps** - Explicit is better
- **Don't commit bazel-* dirs** - Add to .gitignore
- **Don't commit bazel-\* dirs** - Add to .gitignore
- **Don't skip WORKSPACE setup** - Foundation of build
- **Don't ignore build warnings** - Technical debt

View File

@@ -23,6 +23,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
### 1. The Review Mindset
**Goals of Code Review:**
- Catch bugs and edge cases
- Ensure code maintainability
- Share knowledge across team
@@ -31,6 +32,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
- Build team culture
**Not the Goals:**
- Show off knowledge
- Nitpick formatting (use linters)
- Block progress unnecessarily
@@ -39,6 +41,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
### 2. Effective Feedback
**Good Feedback is:**
- Specific and actionable
- Educational, not judgmental
- Focused on the code, not the person
@@ -48,20 +51,21 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
```markdown
❌ Bad: "This is wrong."
✅ Good: "This could cause a race condition when multiple users
access simultaneously. Consider using a mutex here."
access simultaneously. Consider using a mutex here."
❌ Bad: "Why didn't you use X pattern?"
✅ Good: "Have you considered the Repository pattern? It would
make this easier to test. Here's an example: [link]"
make this easier to test. Here's an example: [link]"
❌ Bad: "Rename this variable."
✅ Good: "[nit] Consider `userCount` instead of `uc` for
clarity. Not blocking if you prefer to keep it."
clarity. Not blocking if you prefer to keep it."
```
### 3. Review Scope
**What to Review:**
- Logic correctness and edge cases
- Security vulnerabilities
- Performance implications
@@ -72,6 +76,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
- Architectural fit
**What Not to Review Manually:**
- Code formatting (use Prettier, Black, etc.)
- Import organization
- Linting violations
@@ -159,6 +164,7 @@ For each file:
```markdown
## Security Checklist
- [ ] User input validated and sanitized
- [ ] SQL queries use parameterization
- [ ] Authentication/authorization checked
@@ -166,6 +172,7 @@ For each file:
- [ ] Error messages don't leak info
## Performance Checklist
- [ ] No N+1 queries
- [ ] Database queries indexed
- [ ] Large lists paginated
@@ -173,6 +180,7 @@ For each file:
- [ ] No blocking I/O in hot paths
## Testing Checklist
- [ ] Happy path tested
- [ ] Edge cases covered
- [ ] Error cases tested
@@ -193,28 +201,28 @@ Instead of stating problems, ask questions to encourage thinking:
❌ "This is inefficient."
✅ "I see this loops through all users. Have we considered
the performance impact with 100k users?"
the performance impact with 100k users?"
```
### Technique 3: Suggest, Don't Command
```markdown
````markdown
## Use Collaborative Language
❌ "You must change this to use async/await"
✅ "Suggestion: async/await might make this more readable:
```typescript
`typescript
async function fetchUser(id: string) {
const user = await db.query('SELECT * FROM users WHERE id = ?', id);
return user;
}
```
What do you think?"
`
What do you think?"
❌ "Extract this into a function"
✅ "This logic appears in 3 places. Would it make sense to
extract it into a shared utility function?"
```
extract it into a shared utility function?"
````
### Technique 4: Differentiate Severity
@@ -230,7 +238,7 @@ Use labels to indicate priority:
Example:
"🔴 [blocking] This SQL query is vulnerable to injection.
Please use parameterized queries."
Please use parameterized queries."
"🟢 [nit] Consider renaming `data` to `userData` for clarity."
@@ -389,24 +397,28 @@ test('displays incremented count when clicked', () => {
## Security Review Checklist
### Authentication & Authorization
- [ ] Is authentication required where needed?
- [ ] Are authorization checks before every action?
- [ ] Is JWT validation proper (signature, expiry)?
- [ ] Are API keys/secrets properly secured?
### Input Validation
- [ ] All user inputs validated?
- [ ] File uploads restricted (size, type)?
- [ ] SQL queries parameterized?
- [ ] XSS protection (escape output)?
### Data Protection
- [ ] Passwords hashed (bcrypt/argon2)?
- [ ] Sensitive data encrypted at rest?
- [ ] HTTPS enforced for sensitive data?
- [ ] PII handled according to regulations?
### Common Vulnerabilities
- [ ] No eval() or similar dynamic execution?
- [ ] No hardcoded secrets?
- [ ] CSRF protection for state-changing operations?
@@ -444,14 +456,14 @@ When author disagrees with your feedback:
1. **Seek to Understand**
"Help me understand your approach. What led you to
choose this pattern?"
choose this pattern?"
2. **Acknowledge Valid Points**
"That's a good point about X. I hadn't considered that."
3. **Provide Data**
"I'm concerned about performance. Can we add a benchmark
to validate the approach?"
to validate the approach?"
4. **Escalate if Needed**
"Let's get [architect/senior dev] to weigh in on this."
@@ -488,25 +500,31 @@ When author disagrees with your feedback:
```markdown
## Summary
[Brief overview of what was reviewed]
## Strengths
- [What was done well]
- [Good patterns or approaches]
## Required Changes
🔴 [Blocking issue 1]
🔴 [Blocking issue 2]
## Suggestions
💡 [Improvement 1]
💡 [Improvement 2]
## Questions
❓ [Clarification needed on X]
❓ [Alternative approach consideration]
## Verdict
✅ Approve after addressing required changes
```

View File

@@ -31,11 +31,13 @@ Transform debugging from frustrating guesswork into systematic problem-solving w
### 2. Debugging Mindset
**Don't Assume:**
- "It can't be X" - Yes it can
- "I didn't change Y" - Check anyway
- "It works on my machine" - Find out why
**Do:**
- Reproduce consistently
- Isolate the problem
- Keep detailed notes
@@ -153,58 +155,60 @@ Based on gathered info, ask:
```typescript
// Chrome DevTools Debugger
function processOrder(order: Order) {
debugger; // Execution pauses here
debugger; // Execution pauses here
const total = calculateTotal(order);
console.log('Total:', total);
const total = calculateTotal(order);
console.log("Total:", total);
// Conditional breakpoint
if (order.items.length > 10) {
debugger; // Only breaks if condition true
}
// Conditional breakpoint
if (order.items.length > 10) {
debugger; // Only breaks if condition true
}
return total;
return total;
}
// Console debugging techniques
console.log('Value:', value); // Basic
console.table(arrayOfObjects); // Table format
console.time('operation'); /* code */ console.timeEnd('operation'); // Timing
console.trace(); // Stack trace
console.assert(value > 0, 'Value must be positive'); // Assertion
console.log("Value:", value); // Basic
console.table(arrayOfObjects); // Table format
console.time("operation");
/* code */ console.timeEnd("operation"); // Timing
console.trace(); // Stack trace
console.assert(value > 0, "Value must be positive"); // Assertion
// Performance profiling
performance.mark('start-operation');
performance.mark("start-operation");
// ... operation code
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');
console.log(performance.getEntriesByType('measure'));
performance.mark("end-operation");
performance.measure("operation", "start-operation", "end-operation");
console.log(performance.getEntriesByType("measure"));
```
**VS Code Debugger Configuration:**
```json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Program",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal"
}
]
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Program",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal"
}
]
}
```
@@ -332,14 +336,14 @@ Compare working vs broken:
```markdown
## What's Different?
| Aspect | Working | Broken |
|--------------|-----------------|-----------------|
| Environment | Development | Production |
| Node version | 18.16.0 | 18.15.0 |
| Data | Empty DB | 1M records |
| User | Admin | Regular user |
| Browser | Chrome | Safari |
| Time | During day | After midnight |
| Aspect | Working | Broken |
| ------------ | ----------- | -------------- |
| Environment | Development | Production |
| Node version | 18.16.0 | 18.15.0 |
| Data | Empty DB | 1M records |
| User | Admin | Regular user |
| Browser | Chrome | Safari |
| Time | During day | After midnight |
Hypothesis: Time-based issue? Check timezone handling.
```
@@ -348,24 +352,28 @@ Hypothesis: Time-based issue? Check timezone handling.
```typescript
// Function call tracing
function trace(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
function trace(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned:`, result);
return result;
};
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned:`, result);
return result;
};
return descriptor;
return descriptor;
}
class OrderService {
@trace
calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
@trace
calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
}
```
@@ -380,26 +388,27 @@ class OrderService {
// Node.js memory debugging
if (process.memoryUsage().heapUsed > 500 * 1024 * 1024) {
console.warn('High memory usage:', process.memoryUsage());
console.warn("High memory usage:", process.memoryUsage());
// Generate heap dump
require('v8').writeHeapSnapshot();
// Generate heap dump
require("v8").writeHeapSnapshot();
}
// Find memory leaks in tests
let beforeMemory: number;
beforeEach(() => {
beforeMemory = process.memoryUsage().heapUsed;
beforeMemory = process.memoryUsage().heapUsed;
});
afterEach(() => {
const afterMemory = process.memoryUsage().heapUsed;
const diff = afterMemory - beforeMemory;
const afterMemory = process.memoryUsage().heapUsed;
const diff = afterMemory - beforeMemory;
if (diff > 10 * 1024 * 1024) { // 10MB threshold
console.warn(`Possible memory leak: ${diff / 1024 / 1024}MB`);
}
if (diff > 10 * 1024 * 1024) {
// 10MB threshold
console.warn(`Possible memory leak: ${diff / 1024 / 1024}MB`);
}
});
```

View File

@@ -23,6 +23,7 @@ Build reliable, fast, and maintainable end-to-end test suites that provide confi
### 1. E2E Testing Fundamentals
**What to Test with E2E:**
- Critical user journeys (login, checkout, signup)
- Complex interactions (drag-and-drop, multi-step forms)
- Cross-browser compatibility
@@ -30,6 +31,7 @@ Build reliable, fast, and maintainable end-to-end test suites that provide confi
- Authentication flows
**What NOT to Test with E2E:**
- Unit-level logic (use unit tests)
- API contracts (use integration tests)
- Edge cases (too slow)
@@ -38,6 +40,7 @@ Build reliable, fast, and maintainable end-to-end test suites that provide confi
### 2. Test Philosophy
**The Testing Pyramid:**
```
/\
/E2E\ ← Few, focused on critical paths
@@ -49,6 +52,7 @@ Build reliable, fast, and maintainable end-to-end test suites that provide confi
```
**Best Practices:**
- Test user behavior, not implementation
- Keep tests independent
- Make tests deterministic
@@ -61,34 +65,31 @@ Build reliable, fast, and maintainable end-to-end test suites that provide confi
```typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: './e2e',
timeout: 30000,
expect: {
timeout: 5000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['junit', { outputFile: 'results.xml' }],
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
testDir: "./e2e",
timeout: 30000,
expect: {
timeout: 5000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
{ name: "mobile", use: { ...devices["iPhone 13"] } },
],
});
```
@@ -96,59 +97,58 @@ export default defineConfig({
```typescript
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
import { Page, Locator } from "@playwright/test";
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.loginButton = page.getByRole('button', { name: 'Login' });
this.errorMessage = page.getByRole('alert');
}
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
this.loginButton = page.getByRole("button", { name: "Login" });
this.errorMessage = page.getByRole("alert");
}
async goto() {
await this.page.goto('/login');
}
async goto() {
await this.page.goto("/login");
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async getErrorMessage(): Promise<string> {
return await this.errorMessage.textContent() ?? '';
}
async getErrorMessage(): Promise<string> {
return (await this.errorMessage.textContent()) ?? "";
}
}
// Test using Page Object
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { test, expect } from "@playwright/test";
import { LoginPage } from "./pages/LoginPage";
test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
test("successful login", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login("user@example.com", "password123");
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' }))
.toBeVisible();
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
});
test('failed login shows error', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('invalid@example.com', 'wrong');
test("failed login shows error", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login("invalid@example.com", "wrong");
const error = await loginPage.getErrorMessage();
expect(error).toContain('Invalid credentials');
const error = await loginPage.getErrorMessage();
expect(error).toContain("Invalid credentials");
});
```
@@ -156,56 +156,56 @@ test('failed login shows error', async ({ page }) => {
```typescript
// fixtures/test-data.ts
import { test as base } from '@playwright/test';
import { test as base } from "@playwright/test";
type TestData = {
testUser: {
email: string;
password: string;
name: string;
};
adminUser: {
email: string;
password: string;
};
testUser: {
email: string;
password: string;
name: string;
};
adminUser: {
email: string;
password: string;
};
};
export const test = base.extend<TestData>({
testUser: async ({}, use) => {
const user = {
email: `test-${Date.now()}@example.com`,
password: 'Test123!@#',
name: 'Test User',
};
// Setup: Create user in database
await createTestUser(user);
await use(user);
// Teardown: Clean up user
await deleteTestUser(user.email);
},
testUser: async ({}, use) => {
const user = {
email: `test-${Date.now()}@example.com`,
password: "Test123!@#",
name: "Test User",
};
// Setup: Create user in database
await createTestUser(user);
await use(user);
// Teardown: Clean up user
await deleteTestUser(user.email);
},
adminUser: async ({}, use) => {
await use({
email: 'admin@example.com',
password: process.env.ADMIN_PASSWORD!,
});
},
adminUser: async ({}, use) => {
await use({
email: "admin@example.com",
password: process.env.ADMIN_PASSWORD!,
});
},
});
// Usage in tests
import { test } from './fixtures/test-data';
import { test } from "./fixtures/test-data";
test('user can update profile', async ({ page, testUser }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(testUser.email);
await page.getByLabel('Password').fill(testUser.password);
await page.getByRole('button', { name: 'Login' }).click();
test("user can update profile", async ({ page, testUser }) => {
await page.goto("/login");
await page.getByLabel("Email").fill(testUser.email);
await page.getByLabel("Password").fill(testUser.password);
await page.getByRole("button", { name: "Login" }).click();
await page.goto('/profile');
await page.getByLabel('Name').fill('Updated Name');
await page.getByRole('button', { name: 'Save' }).click();
await page.goto("/profile");
await page.getByLabel("Name").fill("Updated Name");
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByText('Profile updated')).toBeVisible();
await expect(page.getByText("Profile updated")).toBeVisible();
});
```
@@ -213,32 +213,32 @@ test('user can update profile', async ({ page, testUser }) => {
```typescript
// ❌ Bad: Fixed timeouts
await page.waitForTimeout(3000); // Flaky!
await page.waitForTimeout(3000); // Flaky!
// ✅ Good: Wait for specific conditions
await page.waitForLoadState('networkidle');
await page.waitForURL('/dashboard');
await page.waitForLoadState("networkidle");
await page.waitForURL("/dashboard");
await page.waitForSelector('[data-testid="user-profile"]');
// ✅ Better: Auto-waiting with assertions
await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' }))
.toBeEnabled();
await expect(page.getByText("Welcome")).toBeVisible();
await expect(page.getByRole("button", { name: "Submit" })).toBeEnabled();
// Wait for API response
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/users') && response.status() === 200
(response) =>
response.url().includes("/api/users") && response.status() === 200,
);
await page.getByRole('button', { name: 'Load Users' }).click();
await page.getByRole("button", { name: "Load Users" }).click();
const response = await responsePromise;
const data = await response.json();
expect(data.users).toHaveLength(10);
// Wait for multiple conditions
await Promise.all([
page.waitForURL('/success'),
page.waitForLoadState('networkidle'),
expect(page.getByText('Payment successful')).toBeVisible(),
page.waitForURL("/success"),
page.waitForLoadState("networkidle"),
expect(page.getByText("Payment successful")).toBeVisible(),
]);
```
@@ -246,49 +246,49 @@ await Promise.all([
```typescript
// Mock API responses
test('displays error when API fails', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal Server Error' }),
});
test("displays error when API fails", async ({ page }) => {
await page.route("**/api/users", (route) => {
route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "Internal Server Error" }),
});
});
await page.goto('/users');
await expect(page.getByText('Failed to load users')).toBeVisible();
await page.goto("/users");
await expect(page.getByText("Failed to load users")).toBeVisible();
});
// Intercept and modify requests
test('can modify API request', async ({ page }) => {
await page.route('**/api/users', async route => {
const request = route.request();
const postData = JSON.parse(request.postData() || '{}');
test("can modify API request", async ({ page }) => {
await page.route("**/api/users", async (route) => {
const request = route.request();
const postData = JSON.parse(request.postData() || "{}");
// Modify request
postData.role = 'admin';
// Modify request
postData.role = "admin";
await route.continue({
postData: JSON.stringify(postData),
});
await route.continue({
postData: JSON.stringify(postData),
});
});
// Test continues...
// Test continues...
});
// Mock third-party services
test('payment flow with mocked Stripe', async ({ page }) => {
await page.route('**/api/stripe/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({
id: 'mock_payment_id',
status: 'succeeded',
}),
});
test("payment flow with mocked Stripe", async ({ page }) => {
await page.route("**/api/stripe/**", (route) => {
route.fulfill({
status: 200,
body: JSON.stringify({
id: "mock_payment_id",
status: "succeeded",
}),
});
});
// Test payment flow with mocked response
// Test payment flow with mocked response
});
```
@@ -298,21 +298,21 @@ test('payment flow with mocked Stripe', async ({ page }) => {
```typescript
// cypress.config.ts
import { defineConfig } from 'cypress';
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
setupNodeEvents(on, config) {
// Implement node event listeners
},
e2e: {
baseUrl: "http://localhost:3000",
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
setupNodeEvents(on, config) {
// Implement node event listeners
},
},
});
```
@@ -321,68 +321,67 @@ export default defineConfig({
```typescript
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
createUser(userData: UserData): Chainable<User>;
dataCy(value: string): Chainable<JQuery<HTMLElement>>;
}
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
createUser(userData: UserData): Chainable<User>;
dataCy(value: string): Chainable<JQuery<HTMLElement>>;
}
}
}
Cypress.Commands.add('login', (email: string, password: string) => {
cy.visit('/login');
cy.get('[data-testid="email"]').type(email);
cy.get('[data-testid="password"]').type(password);
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
Cypress.Commands.add("login", (email: string, password: string) => {
cy.visit("/login");
cy.get('[data-testid="email"]').type(email);
cy.get('[data-testid="password"]').type(password);
cy.get('[data-testid="login-button"]').click();
cy.url().should("include", "/dashboard");
});
Cypress.Commands.add('createUser', (userData: UserData) => {
return cy.request('POST', '/api/users', userData)
.its('body');
Cypress.Commands.add("createUser", (userData: UserData) => {
return cy.request("POST", "/api/users", userData).its("body");
});
Cypress.Commands.add('dataCy', (value: string) => {
return cy.get(`[data-cy="${value}"]`);
Cypress.Commands.add("dataCy", (value: string) => {
return cy.get(`[data-cy="${value}"]`);
});
// Usage
cy.login('user@example.com', 'password');
cy.dataCy('submit-button').click();
cy.login("user@example.com", "password");
cy.dataCy("submit-button").click();
```
### Pattern 2: Cypress Intercept
```typescript
// Mock API calls
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
],
}).as('getUsers');
cy.intercept("GET", "/api/users", {
statusCode: 200,
body: [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" },
],
}).as("getUsers");
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-testid="user-list"]').children().should('have.length', 2);
cy.visit("/users");
cy.wait("@getUsers");
cy.get('[data-testid="user-list"]').children().should("have.length", 2);
// Modify responses
cy.intercept('GET', '/api/users', (req) => {
req.reply((res) => {
// Modify response
res.body.users = res.body.users.slice(0, 5);
res.send();
});
cy.intercept("GET", "/api/users", (req) => {
req.reply((res) => {
// Modify response
res.body.users = res.body.users.slice(0, 5);
res.send();
});
});
// Simulate slow network
cy.intercept('GET', '/api/data', (req) => {
req.reply((res) => {
res.delay(3000); // 3 second delay
res.send();
});
cy.intercept("GET", "/api/data", (req) => {
req.reply((res) => {
res.delay(3000); // 3 second delay
res.send();
});
});
```
@@ -392,31 +391,31 @@ cy.intercept('GET', '/api/data', (req) => {
```typescript
// With Playwright
import { test, expect } from '@playwright/test';
import { test, expect } from "@playwright/test";
test('homepage looks correct', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixels: 100,
});
test("homepage looks correct", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveScreenshot("homepage.png", {
fullPage: true,
maxDiffPixels: 100,
});
});
test('button in all states', async ({ page }) => {
await page.goto('/components');
test("button in all states", async ({ page }) => {
await page.goto("/components");
const button = page.getByRole('button', { name: 'Submit' });
const button = page.getByRole("button", { name: "Submit" });
// Default state
await expect(button).toHaveScreenshot('button-default.png');
// Default state
await expect(button).toHaveScreenshot("button-default.png");
// Hover state
await button.hover();
await expect(button).toHaveScreenshot('button-hover.png');
// Hover state
await button.hover();
await expect(button).toHaveScreenshot("button-hover.png");
// Disabled state
await button.evaluate(el => el.setAttribute('disabled', 'true'));
await expect(button).toHaveScreenshot('button-disabled.png');
// Disabled state
await button.evaluate((el) => el.setAttribute("disabled", "true"));
await expect(button).toHaveScreenshot("button-disabled.png");
});
```
@@ -425,20 +424,20 @@ test('button in all states', async ({ page }) => {
```typescript
// playwright.config.ts
export default defineConfig({
projects: [
{
name: 'shard-1',
use: { ...devices['Desktop Chrome'] },
grepInvert: /@slow/,
shard: { current: 1, total: 4 },
},
{
name: 'shard-2',
use: { ...devices['Desktop Chrome'] },
shard: { current: 2, total: 4 },
},
// ... more shards
],
projects: [
{
name: "shard-1",
use: { ...devices["Desktop Chrome"] },
grepInvert: /@slow/,
shard: { current: 1, total: 4 },
},
{
name: "shard-2",
use: { ...devices["Desktop Chrome"] },
shard: { current: 2, total: 4 },
},
// ... more shards
],
});
// Run in CI
@@ -450,27 +449,25 @@ export default defineConfig({
```typescript
// Install: npm install @axe-core/playwright
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test('page should not have accessibility violations', async ({ page }) => {
await page.goto('/');
test("page should not have accessibility violations", async ({ page }) => {
await page.goto("/");
const accessibilityScanResults = await new AxeBuilder({ page })
.exclude('#third-party-widget')
.analyze();
const accessibilityScanResults = await new AxeBuilder({ page })
.exclude("#third-party-widget")
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
expect(accessibilityScanResults.violations).toEqual([]);
});
test('form is accessible', async ({ page }) => {
await page.goto('/signup');
test("form is accessible", async ({ page }) => {
await page.goto("/signup");
const results = await new AxeBuilder({ page })
.include('form')
.analyze();
const results = await new AxeBuilder({ page }).include("form").analyze();
expect(results.violations).toEqual([]);
expect(results.violations).toEqual([]);
});
```
@@ -487,13 +484,13 @@ test('form is accessible', async ({ page }) => {
```typescript
// ❌ Bad selectors
cy.get('.btn.btn-primary.submit-button').click();
cy.get('div > form > div:nth-child(2) > input').type('text');
cy.get(".btn.btn-primary.submit-button").click();
cy.get("div > form > div:nth-child(2) > input").type("text");
// ✅ Good selectors
cy.getByRole('button', { name: 'Submit' }).click();
cy.getByLabel('Email address').type('user@example.com');
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.getByRole("button", { name: "Submit" }).click();
cy.getByLabel("Email address").type("user@example.com");
cy.get('[data-testid="email-input"]').type("user@example.com");
```
## Common Pitfalls

View File

@@ -23,12 +23,14 @@ Build resilient applications with robust error handling strategies that graceful
### 1. Error Handling Philosophies
**Exceptions vs Result Types:**
- **Exceptions**: Traditional try-catch, disrupts control flow
- **Result Types**: Explicit success/failure, functional approach
- **Error Codes**: C-style, requires discipline
- **Option/Maybe Types**: For nullable values
**When to Use Each:**
- Exceptions: Unexpected errors, exceptional conditions
- Result Types: Expected errors, validation failures
- Panics/Crashes: Unrecoverable errors, programming bugs
@@ -36,12 +38,14 @@ Build resilient applications with robust error handling strategies that graceful
### 2. Error Categories
**Recoverable Errors:**
- Network timeouts
- Missing files
- Invalid user input
- API rate limits
**Unrecoverable Errors:**
- Out of memory
- Stack overflow
- Programming bugs (null pointer, etc.)
@@ -51,6 +55,7 @@ Build resilient applications with robust error handling strategies that graceful
### Python Error Handling
**Custom Exception Hierarchy:**
```python
class ApplicationError(Exception):
"""Base exception for all application errors."""
@@ -87,6 +92,7 @@ def get_user(user_id: str) -> User:
```
**Context Managers for Cleanup:**
```python
from contextlib import contextmanager
@@ -110,6 +116,7 @@ with database_transaction(db.session) as session:
```
**Retry with Exponential Backoff:**
```python
import time
from functools import wraps
@@ -152,131 +159,128 @@ def fetch_data(url: str) -> dict:
### TypeScript/JavaScript Error Handling
**Custom Error Classes:**
```typescript
// Custom error classes
class ApplicationError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public details?: Record<string, any>
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public details?: Record<string, any>,
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends ApplicationError {
constructor(message: string, details?: Record<string, any>) {
super(message, 'VALIDATION_ERROR', 400, details);
}
constructor(message: string, details?: Record<string, any>) {
super(message, "VALIDATION_ERROR", 400, details);
}
}
class NotFoundError extends ApplicationError {
constructor(resource: string, id: string) {
super(
`${resource} not found`,
'NOT_FOUND',
404,
{ resource, id }
);
}
constructor(resource: string, id: string) {
super(`${resource} not found`, "NOT_FOUND", 404, { resource, id });
}
}
// Usage
function getUser(id: string): User {
const user = users.find(u => u.id === id);
if (!user) {
throw new NotFoundError('User', id);
}
return user;
const user = users.find((u) => u.id === id);
if (!user) {
throw new NotFoundError("User", id);
}
return user;
}
```
**Result Type Pattern:**
```typescript
// Result type for explicit error handling
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
// Helper functions
function Ok<T>(value: T): Result<T, never> {
return { ok: true, value };
return { ok: true, value };
}
function Err<E>(error: E): Result<never, E> {
return { ok: false, error };
return { ok: false, error };
}
// Usage
function parseJSON<T>(json: string): Result<T, SyntaxError> {
try {
const value = JSON.parse(json) as T;
return Ok(value);
} catch (error) {
return Err(error as SyntaxError);
}
try {
const value = JSON.parse(json) as T;
return Ok(value);
} catch (error) {
return Err(error as SyntaxError);
}
}
// Consuming Result
const result = parseJSON<User>(userJson);
if (result.ok) {
console.log(result.value.name);
console.log(result.value.name);
} else {
console.error('Parse failed:', result.error.message);
console.error("Parse failed:", result.error.message);
}
// Chaining Results
function chain<T, U, E>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>
result: Result<T, E>,
fn: (value: T) => Result<U, E>,
): Result<U, E> {
return result.ok ? fn(result.value) : result;
return result.ok ? fn(result.value) : result;
}
```
**Async Error Handling:**
```typescript
// Async/await with proper error handling
async function fetchUserOrders(userId: string): Promise<Order[]> {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
return orders;
} catch (error) {
if (error instanceof NotFoundError) {
return []; // Return empty array for not found
}
if (error instanceof NetworkError) {
// Retry logic
return retryFetchOrders(userId);
}
// Re-throw unexpected errors
throw error;
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
return orders;
} catch (error) {
if (error instanceof NotFoundError) {
return []; // Return empty array for not found
}
if (error instanceof NetworkError) {
// Retry logic
return retryFetchOrders(userId);
}
// Re-throw unexpected errors
throw error;
}
}
// Promise error handling
function fetchData(url: string): Promise<Data> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new NetworkError(`HTTP ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error('Fetch failed:', error);
throw error;
});
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new NetworkError(`HTTP ${response.status}`);
}
return response.json();
})
.catch((error) => {
console.error("Fetch failed:", error);
throw error;
});
}
```
### Rust Error Handling
**Result and Option Types:**
```rust
use std::fs::File;
use std::io::{self, Read};
@@ -328,6 +332,7 @@ fn get_user_age(id: &str) -> Result<u32, AppError> {
### Go Error Handling
**Explicit Error Returns:**
```go
// Basic error handling
func getUser(id string) (*User, error) {
@@ -464,54 +469,54 @@ Collect multiple errors instead of failing on first error.
```typescript
class ErrorCollector {
private errors: Error[] = [];
private errors: Error[] = [];
add(error: Error): void {
this.errors.push(error);
}
add(error: Error): void {
this.errors.push(error);
}
hasErrors(): boolean {
return this.errors.length > 0;
}
hasErrors(): boolean {
return this.errors.length > 0;
}
getErrors(): Error[] {
return [...this.errors];
}
getErrors(): Error[] {
return [...this.errors];
}
throw(): never {
if (this.errors.length === 1) {
throw this.errors[0];
}
throw new AggregateError(
this.errors,
`${this.errors.length} errors occurred`
);
throw(): never {
if (this.errors.length === 1) {
throw this.errors[0];
}
throw new AggregateError(
this.errors,
`${this.errors.length} errors occurred`,
);
}
}
// Usage: Validate multiple fields
function validateUser(data: any): User {
const errors = new ErrorCollector();
const errors = new ErrorCollector();
if (!data.email) {
errors.add(new ValidationError('Email is required'));
} else if (!isValidEmail(data.email)) {
errors.add(new ValidationError('Email is invalid'));
}
if (!data.email) {
errors.add(new ValidationError("Email is required"));
} else if (!isValidEmail(data.email)) {
errors.add(new ValidationError("Email is invalid"));
}
if (!data.name || data.name.length < 2) {
errors.add(new ValidationError('Name must be at least 2 characters'));
}
if (!data.name || data.name.length < 2) {
errors.add(new ValidationError("Name must be at least 2 characters"));
}
if (!data.age || data.age < 18) {
errors.add(new ValidationError('Age must be 18 or older'));
}
if (!data.age || data.age < 18) {
errors.add(new ValidationError("Age must be 18 or older"));
}
if (errors.hasErrors()) {
errors.throw();
}
if (errors.hasErrors()) {
errors.throw();
}
return data as User;
return data as User;
}
```

View File

@@ -25,6 +25,7 @@ Master advanced Git techniques to maintain clean history, collaborate effectivel
Interactive rebase is the Swiss Army knife of Git history editing.
**Common Operations:**
- `pick`: Keep commit as-is
- `reword`: Change commit message
- `edit`: Amend commit content
@@ -33,6 +34,7 @@ Interactive rebase is the Swiss Army knife of Git history editing.
- `drop`: Remove commit entirely
**Basic Usage:**
```bash
# Rebase last 5 commits
git rebase -i HEAD~5
@@ -86,6 +88,7 @@ git bisect reset
```
**Automated Bisect:**
```bash
# Use script to test automatically
git bisect start HEAD v1.0.0
@@ -251,11 +254,13 @@ git branch recovery def456
### Rebase vs Merge Strategy
**When to Rebase:**
- Cleaning up local commits before pushing
- Keeping feature branch up-to-date with main
- Creating linear history for easier review
**When to Merge:**
- Integrating completed features into main
- Preserving exact history of collaboration
- Public branches used by others

View File

@@ -23,6 +23,7 @@ Build efficient, scalable monorepos that enable code sharing, consistent tooling
### 1. Why Monorepos?
**Advantages:**
- Shared code and dependencies
- Atomic commits across projects
- Consistent tooling and standards
@@ -31,6 +32,7 @@ Build efficient, scalable monorepos that enable code sharing, consistent tooling
- Better code visibility
**Challenges:**
- Build performance at scale
- CI/CD complexity
- Access control
@@ -39,11 +41,13 @@ Build efficient, scalable monorepos that enable code sharing, consistent tooling
### 2. Monorepo Tools
**Package Managers:**
- pnpm workspaces (recommended)
- npm workspaces
- Yarn workspaces
**Build Systems:**
- Turborepo (recommended for most)
- Nx (feature-rich, complex)
- Lerna (older, maintenance mode)
@@ -105,10 +109,7 @@ cd my-monorepo
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
@@ -170,9 +171,9 @@ cd my-monorepo
```yaml
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
- "apps/*"
- "packages/*"
- "tools/*"
```
```json
@@ -346,35 +347,35 @@ nx run-many --target=build --all --parallel=3
// packages/config/eslint-preset.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier",
],
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
parser: '@typescript-eslint/parser',
plugins: ["@typescript-eslint", "react", "react-hooks"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
version: "detect",
},
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'react/react-in-jsx-scope': 'off',
"@typescript-eslint/no-unused-vars": "error",
"react/react-in-jsx-scope": "off",
},
};
// apps/web/.eslintrc.js
module.exports = {
extends: ['@repo/config/eslint-preset'],
extends: ["@repo/config/eslint-preset"],
rules: {
// App-specific rules
},
@@ -427,16 +428,16 @@ export function capitalize(str: string): string {
}
export function truncate(str: string, length: number): string {
return str.length > length ? str.slice(0, length) + '...' : str;
return str.length > length ? str.slice(0, length) + "..." : str;
}
// packages/utils/src/index.ts
export * from './string';
export * from './array';
export * from './date';
export * from "./string";
export * from "./array";
export * from "./date";
// Usage in apps
import { capitalize, truncate } from '@repo/utils';
import { capitalize, truncate } from "@repo/utils";
```
### Pattern 3: Shared Types
@@ -447,7 +448,7 @@ export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'user';
role: "admin" | "user";
}
export interface CreateUserInput {
@@ -457,7 +458,7 @@ export interface CreateUserInput {
}
// Used in both frontend and backend
import type { User, CreateUserInput } from '@repo/types';
import type { User, CreateUserInput } from "@repo/types";
```
## Build Optimization
@@ -525,7 +526,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # For Nx affected commands
fetch-depth: 0 # For Nx affected commands
- uses: pnpm/action-setup@v2
with:
@@ -534,7 +535,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -39,13 +39,13 @@ workspace/
### 2. Library Types
| Type | Purpose | Example |
|------|---------|---------|
| **feature** | Smart components, business logic | `feature-auth` |
| **ui** | Presentational components | `ui-buttons` |
| **data-access** | API calls, state management | `data-access-users` |
| **util** | Pure functions, helpers | `util-formatting` |
| **shell** | App bootstrapping | `shell-web` |
| Type | Purpose | Example |
| --------------- | -------------------------------- | ------------------- |
| **feature** | Smart components, business logic | `feature-auth` |
| **ui** | Presentational components | `ui-buttons` |
| **data-access** | API calls, state management | `data-access-users` |
| **util** | Pure functions, helpers | `util-formatting` |
| **shell** | App bootstrapping | `shell-web` |
## Templates
@@ -276,8 +276,8 @@ import {
joinPathFragments,
names,
readProjectConfiguration,
} from '@nx/devkit';
import { libraryGenerator } from '@nx/react';
} from "@nx/devkit";
import { libraryGenerator } from "@nx/react";
interface FeatureLibraryGeneratorSchema {
name: string;
@@ -287,7 +287,7 @@ interface FeatureLibraryGeneratorSchema {
export default async function featureLibraryGenerator(
tree: Tree,
options: FeatureLibraryGeneratorSchema
options: FeatureLibraryGeneratorSchema,
) {
const { name, scope, directory } = options;
const projectDirectory = directory
@@ -299,26 +299,29 @@ export default async function featureLibraryGenerator(
name: `feature-${name}`,
directory: projectDirectory,
tags: `type:feature,scope:${scope}`,
style: 'css',
style: "css",
skipTsConfig: false,
skipFormat: true,
unitTestRunner: 'jest',
linter: 'eslint',
unitTestRunner: "jest",
linter: "eslint",
});
// Add custom files
const projectConfig = readProjectConfiguration(tree, `${scope}-feature-${name}`);
const projectConfig = readProjectConfiguration(
tree,
`${scope}-feature-${name}`,
);
const projectNames = names(name);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
joinPathFragments(__dirname, "files"),
projectConfig.sourceRoot,
{
...projectNames,
scope,
tmpl: '',
}
tmpl: "",
},
);
await formatFiles(tree);
@@ -351,7 +354,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache: "npm"
- name: Install dependencies
run: npm ci
@@ -433,6 +436,7 @@ nx migrate --run-migrations
## Best Practices
### Do's
- **Use tags consistently** - Enforce with module boundaries
- **Enable caching early** - Significant CI savings
- **Keep libs focused** - Single responsibility
@@ -440,6 +444,7 @@ nx migrate --run-migrations
- **Document boundaries** - Help new developers
### Don'ts
- **Don't create circular deps** - Graph should be acyclic
- **Don't skip affected** - Test only what changed
- **Don't ignore boundaries** - Tech debt accumulates

View File

@@ -25,6 +25,7 @@ Transform slow database queries into lightning-fast operations through systemati
Understanding EXPLAIN output is fundamental to optimization.
**PostgreSQL EXPLAIN:**
```sql
-- Basic explain
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
@@ -42,6 +43,7 @@ WHERE u.created_at > NOW() - INTERVAL '30 days';
```
**Key Metrics to Watch:**
- **Seq Scan**: Full table scan (usually slow for large tables)
- **Index Scan**: Using index (good)
- **Index Only Scan**: Using index without touching table (best)
@@ -57,6 +59,7 @@ WHERE u.created_at > NOW() - INTERVAL '30 days';
Indexes are the most powerful optimization tool.
**Index Types:**
- **B-Tree**: Default, good for equality and range queries
- **Hash**: Only for equality (=) comparisons
- **GIN**: Full-text search, array queries, JSONB
@@ -92,6 +95,7 @@ CREATE INDEX idx_metadata ON events USING GIN(metadata);
### 3. Query Optimization Patterns
**Avoid SELECT \*:**
```sql
-- Bad: Fetches unnecessary columns
SELECT * FROM users WHERE id = 123;
@@ -101,6 +105,7 @@ SELECT id, email, name FROM users WHERE id = 123;
```
**Use WHERE Clause Efficiently:**
```sql
-- Bad: Function prevents index usage
SELECT * FROM users WHERE LOWER(email) = 'user@example.com';
@@ -115,6 +120,7 @@ SELECT * FROM users WHERE email = 'user@example.com';
```
**Optimize JOINs:**
```sql
-- Bad: Cartesian product then filter
SELECT u.name, o.total
@@ -138,6 +144,7 @@ JOIN orders o ON u.id = o.user_id;
### Pattern 1: Eliminate N+1 Queries
**Problem: N+1 Query Anti-Pattern**
```python
# Bad: Executes N+1 queries
users = db.query("SELECT * FROM users LIMIT 10")
@@ -147,6 +154,7 @@ for user in users:
```
**Solution: Use JOINs or Batch Loading**
```sql
-- Solution 1: JOIN
SELECT
@@ -187,6 +195,7 @@ for order in orders:
### Pattern 2: Optimize Pagination
**Bad: OFFSET on Large Tables**
```sql
-- Slow for large offsets
SELECT * FROM users
@@ -195,6 +204,7 @@ LIMIT 20 OFFSET 100000; -- Very slow!
```
**Good: Cursor-Based Pagination**
```sql
-- Much faster: Use cursor (last seen ID)
SELECT * FROM users
@@ -215,6 +225,7 @@ CREATE INDEX idx_users_cursor ON users(created_at DESC, id DESC);
### Pattern 3: Aggregate Efficiently
**Optimize COUNT Queries:**
```sql
-- Bad: Counts all rows
SELECT COUNT(*) FROM orders; -- Slow on large tables
@@ -235,6 +246,7 @@ WHERE created_at > NOW() - INTERVAL '7 days';
```
**Optimize GROUP BY:**
```sql
-- Bad: Group by then filter
SELECT user_id, COUNT(*) as order_count
@@ -256,6 +268,7 @@ CREATE INDEX idx_orders_user_status ON orders(user_id, status);
### Pattern 4: Subquery Optimization
**Transform Correlated Subqueries:**
```sql
-- Bad: Correlated subquery (runs for each row)
SELECT u.name, u.email,
@@ -277,6 +290,7 @@ LEFT JOIN orders o ON o.user_id = u.id;
```
**Use CTEs for Clarity:**
```sql
-- Using Common Table Expressions
WITH recent_users AS (
@@ -298,6 +312,7 @@ LEFT JOIN user_order_counts uoc ON ru.id = uoc.user_id;
### Pattern 5: Batch Operations
**Batch INSERT:**
```sql
-- Bad: Multiple individual inserts
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
@@ -315,6 +330,7 @@ COPY users (name, email) FROM '/tmp/users.csv' CSV HEADER;
```
**Batch UPDATE:**
```sql
-- Bad: Update in loop
UPDATE users SET status = 'active' WHERE id = 1;

View File

@@ -38,12 +38,12 @@ Workspace Root/
### 2. Pipeline Concepts
| Concept | Description |
|---------|-------------|
| **dependsOn** | Tasks that must complete first |
| **cache** | Whether to cache outputs |
| **outputs** | Files to cache |
| **inputs** | Files that affect cache key |
| Concept | Description |
| -------------- | -------------------------------- |
| **dependsOn** | Tasks that must complete first |
| **cache** | Whether to cache outputs |
| **outputs** | Files to cache |
| **inputs** | Files that affect cache key |
| **persistent** | Long-running tasks (dev servers) |
## Templates
@@ -53,35 +53,18 @@ Workspace Root/
```json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
".env",
".env.local"
],
"globalEnv": [
"NODE_ENV",
"VERCEL_URL"
],
"globalDependencies": [".env", ".env.local"],
"globalEnv": ["NODE_ENV", "VERCEL_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
".next/**",
"!.next/cache/**"
],
"env": [
"API_URL",
"NEXT_PUBLIC_*"
]
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["API_URL", "NEXT_PUBLIC_*"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": [
"src/**/*.tsx",
"src/**/*.ts",
"test/**/*.ts"
]
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"outputs": [],
@@ -112,18 +95,11 @@ Workspace Root/
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"],
"env": [
"NEXT_PUBLIC_API_URL",
"NEXT_PUBLIC_ANALYTICS_ID"
]
"env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_ANALYTICS_ID"]
},
"test": {
"outputs": ["coverage/**"],
"inputs": [
"src/**",
"tests/**",
"jest.config.js"
]
"inputs": ["src/**", "tests/**", "jest.config.js"]
}
}
}
@@ -168,7 +144,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache: "npm"
- name: Install dependencies
run: npm ci
@@ -184,32 +160,32 @@ jobs:
```typescript
// Custom remote cache server (Express)
import express from 'express';
import { createReadStream, createWriteStream } from 'fs';
import { mkdir } from 'fs/promises';
import { join } from 'path';
import express from "express";
import { createReadStream, createWriteStream } from "fs";
import { mkdir } from "fs/promises";
import { join } from "path";
const app = express();
const CACHE_DIR = './cache';
const CACHE_DIR = "./cache";
// Get artifact
app.get('/v8/artifacts/:hash', async (req, res) => {
app.get("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || 'default';
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
const stream = createReadStream(filePath);
stream.pipe(res);
} catch {
res.status(404).send('Not found');
res.status(404).send("Not found");
}
});
// Put artifact
app.put('/v8/artifacts/:hash', async (req, res) => {
app.put("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || 'default';
const team = req.query.teamId || "default";
const dir = join(CACHE_DIR, team);
const filePath = join(dir, hash);
@@ -218,15 +194,17 @@ app.put('/v8/artifacts/:hash', async (req, res) => {
const stream = createWriteStream(filePath);
req.pipe(stream);
stream.on('finish', () => {
res.json({ urls: [`${req.protocol}://${req.get('host')}/v8/artifacts/${hash}`] });
stream.on("finish", () => {
res.json({
urls: [`${req.protocol}://${req.get("host")}/v8/artifacts/${hash}`],
});
});
});
// Check artifact exists
app.head('/v8/artifacts/:hash', async (req, res) => {
app.head("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || 'default';
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
@@ -291,20 +269,12 @@ turbo build --filter='...[HEAD^1]...'
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": [
"$TURBO_DEFAULT$",
"!**/*.md",
"!**/*.test.*"
]
"inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.*"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": [
"src/**",
"tests/**",
"*.config.*"
],
"inputs": ["src/**", "tests/**", "*.config.*"],
"env": ["CI", "NODE_ENV"]
},
"test:e2e": {
@@ -339,10 +309,7 @@ turbo build --filter='...[HEAD^1]...'
{
"name": "my-turborepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
@@ -388,6 +355,7 @@ TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web
## Best Practices
### Do's
- **Define explicit inputs** - Avoid cache invalidation
- **Use workspace protocol** - `"@myorg/ui": "workspace:*"`
- **Enable remote caching** - Share across CI and local
@@ -395,6 +363,7 @@ TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web
- **Cache build outputs** - Not source files
### Don'ts
- **Don't cache dev servers** - Use `persistent: true`
- **Don't include secrets in env** - Use runtime env vars
- **Don't ignore dependsOn** - Causes race conditions