Agent skill
solid-principles
SOLID principles adapted for functional and TypeScript-first development.
Install this agent skill to your Project
npx add-skill https://github.com/aiskillstore/marketplace/tree/main/skills/doubleslashse/solid-principles
SKILL.md
SOLID Principles for Node.js/TypeScript
Overview
SOLID principles adapted for functional and TypeScript-first development.
S - Single Responsibility Principle
A module/function should have only one reason to change.
Violation
// Bad: Does validation, processing, and notification
const processOrder = async (order: Order) => {
// Validation
if (!order.items.length) throw new Error('Empty order');
if (order.total < 0) throw new Error('Invalid total');
// Processing
const processed = { ...order, status: 'processed' };
await db.orders.save(processed);
// Notification
await emailService.send(order.userId, 'Order confirmed');
return processed;
};
Correct
// Good: Separate responsibilities
const validateOrder = (order: Order): Result<Order, ValidationError> => {
if (!order.items.length) return Result.fail(emptyOrderError());
if (order.total < 0) return Result.fail(invalidTotalError());
return Result.ok(order);
};
const saveOrder = (db: Database) =>
async (order: Order): Promise<Order> => {
const processed = { ...order, status: 'processed' };
await db.orders.save(processed);
return processed;
};
const notifyUser = (notifier: Notifier) =>
async (userId: string, message: string): Promise<void> => {
await notifier.send(userId, message);
};
// Compose in orchestrator
const processOrder = async (order: Order) => {
const validation = validateOrder(order);
if (validation.isFailure) return validation;
const saved = await saveOrder(db)(validation.value);
await notifyUser(emailService)(saved.userId, 'Order confirmed');
return Result.ok(saved);
};
O - Open/Closed Principle
Open for extension, closed for modification.
Violation
// Bad: Must modify function to add new discount types
const calculateDiscount = (type: string, amount: number): number => {
if (type === 'percentage') return amount * 0.1;
if (type === 'fixed') return 10;
if (type === 'loyalty') return amount * 0.15;
return 0;
};
Correct
// Good: Extend via new strategies without modifying existing code
type DiscountStrategy = (amount: number) => number;
const discountStrategies: Record<string, DiscountStrategy> = {
percentage: (amount) => amount * 0.1,
fixed: () => 10,
loyalty: (amount) => amount * 0.15,
};
// Easy to extend
discountStrategies.holiday = (amount) => amount * 0.25;
const calculateDiscount = (type: string, amount: number): number =>
discountStrategies[type]?.(amount) ?? 0;
L - Liskov Substitution Principle
Subtypes must be substitutable for their base types.
Violation
// Bad: Square breaks Rectangle contract
class Rectangle {
constructor(public width: number, public height: number) {}
setWidth(w: number) { this.width = w; }
setHeight(h: number) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w: number) {
this.width = w;
this.height = w; // Breaks expectation!
}
}
Correct
// Good: Use composition and explicit types
type Shape = {
area: () => number;
};
const createRectangle = (width: number, height: number): Shape => ({
area: () => width * height,
});
const createSquare = (side: number): Shape => ({
area: () => side * side,
});
I - Interface Segregation Principle
Clients should not depend on interfaces they don't use.
Violation
// Bad: Fat interface
interface DataService {
read(id: string): Promise<Data>;
write(data: Data): Promise<void>;
delete(id: string): Promise<void>;
backup(): Promise<void>;
restore(): Promise<void>;
migrate(): Promise<void>;
}
// Client only needs read
const reportGenerator = (service: DataService) => {
// Only uses service.read(), but depends on entire interface
};
Correct
// Good: Segregated interfaces
type Reader<T> = {
read: (id: string) => Promise<T>;
};
type Writer<T> = {
write: (data: T) => Promise<void>;
};
type Deletable = {
delete: (id: string) => Promise<void>;
};
// Client depends only on what it needs
const reportGenerator = (reader: Reader<ReportData>) => {
// Only depends on read capability
};
// Compose interfaces as needed
type DataService = Reader<Data> & Writer<Data> & Deletable;
D - Dependency Inversion Principle
Depend on abstractions, not concretions.
Violation
// Bad: Direct dependency on implementation
import { PrismaClient } from '@prisma/client';
const createUserService = () => {
const prisma = new PrismaClient(); // Hardcoded!
return {
findUser: (id: string) => prisma.user.findFirst({ where: { id } }),
};
};
Correct
// Good: Depend on abstraction
type UserRepository = {
findById: (id: string) => Promise<User | null>;
save: (user: User) => Promise<User>;
};
const createUserService = (repo: UserRepository) => ({
findUser: (id: string) => repo.findById(id),
createUser: async (data: CreateUserData) => {
const user = { id: generateId(), ...data };
return repo.save(user);
},
});
// Inject implementation
const prismaRepo: UserRepository = {
findById: (id) => prisma.user.findFirst({ where: { id } }),
save: (user) => prisma.user.create({ data: user }),
};
const service = createUserService(prismaRepo);
SOLID in Practice
Factory Function Pattern
// Follows all SOLID principles
type Dependencies = {
userRepo: UserRepository;
orderRepo: OrderRepository;
paymentGateway: PaymentGateway;
logger: Logger;
};
const createOrderProcessor = (deps: Dependencies) => {
const validateOrder = (order: Order): Result<Order, ValidationError> => {
// Single responsibility: validation only
};
const processPayment = async (order: Order): Promise<Result<Payment, PaymentError>> => {
// Single responsibility: payment only
};
return {
process: async (order: Order): Promise<Result<ProcessedOrder, OrderError>> => {
const validation = validateOrder(order);
if (validation.isFailure) return validation;
const payment = await processPayment(validation.value);
if (payment.isFailure) return payment;
// Compose results
return Result.ok({ order: validation.value, payment: payment.value });
},
};
};
Testing SOLID Code
describe('OrderProcessor', () => {
it('should process valid order', async () => {
// Easy to test due to dependency injection
const deps = {
userRepo: createFakeUserRepo(),
orderRepo: createFakeOrderRepo(),
paymentGateway: { charge: jest.fn().mockResolvedValue(Result.ok({})) },
logger: { info: jest.fn() },
};
const processor = createOrderProcessor(deps);
const result = await processor.process(createTestOrder());
expect(result.isSuccess).toBe(true);
});
});
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
perigon-backend
Perigon ASP.NET Core + EF Core + Aspire conventions
perigon-agent
Pointers for Copilot/agents to apply Perigon conventions
perigon-angular
Angular 21+ standalone/Material/signal conventions for Perigon WebApp
fastapi-mastery
Comprehensive FastAPI development skill covering REST API creation, routing, request/response handling, validation, authentication, database integration, middleware, and deployment. Use when working with FastAPI projects, building APIs, implementing CRUD operations, setting up authentication/authorization, integrating databases (SQL/NoSQL), adding middleware, handling WebSockets, or deploying FastAPI applications. Triggered by requests involving .py files with FastAPI code, API endpoint creation, Pydantic models, or FastAPI-specific features.
context7-efficient
Token-efficient library documentation fetcher using Context7 MCP with 86.8% token savings through intelligent shell pipeline filtering. Fetches code examples, API references, and best practices for JavaScript, Python, Go, Rust, and other libraries. Use when users ask about library documentation, need code examples, want API usage patterns, are learning a new framework, need syntax reference, or troubleshooting with library-specific information. Triggers include questions like "Show me React hooks", "How do I use Prisma", "What's the Next.js routing syntax", or any request for library/framework documentation.
browser-use
Browser automation using Playwright MCP. Navigate websites, fill forms, click elements, take screenshots, and extract data. Use when tasks require web browsing, form submission, web scraping, UI testing, or any browser interaction.
Didn't find tool you were looking for?