d4ea931bf8
* feat(agents): add modular engineering rules from 2026 standards Add a rules directory with individual rule files derived from the Cal.com Engineering in 2026 and Beyond blog post. Rules are organized by section (architecture, quality, data, api, performance, testing, patterns, culture) following the Vercel agent-skills structure. Includes: - _sections.md defining rule categories and impact levels - _template.md for creating new rules - 14 individual rule files covering key engineering standards - README documenting the rules structure and usage Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(agents): consolidate DI and Repository+DTO docs into rules - Move di-pattern.md content to rules/patterns-di-pattern.md - Extract Repository + DTO section from knowledge-base.md into: - rules/data-repository-methods.md (method naming conventions) - rules/data-dto-boundaries.md (DTO location and naming) - Update knowledge-base.md to reference the new rule files - Delete old di-pattern.md file Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore(agents): remove stub reference sections from knowledge-base.md The rules directory is self-contained with its own README, so these redirect sections are unnecessary clutter. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(agents): combine DI pattern rules into single file Merged patterns-di-pattern.md into patterns-dependency-injection.md to eliminate overlap and create one comprehensive DI guide. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
3.4 KiB
3.4 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Use DTOs at Every Architectural Boundary | CRITICAL | Prevents technology coupling and security risks | data, dto, boundaries, security, types |
Use DTOs at Every Architectural Boundary
Impact: CRITICAL
Database types should not leak to the frontend. This has become a popular shortcut in our tech stack, but it's a code smell that creates multiple problems.
Problems with leaking database types:
- Technology coupling (Prisma types end up in React components)
- Security risks (accidental leakage of sensitive fields)
- Fragile contracts between server and client
- Inability to evolve the database schema independently
Incorrect (database types leaking):
// API route returning Prisma types directly
import type { User } from "@prisma/client";
export async function GET(): Promise<User> {
const user = await prisma.user.findFirst();
return user; // Leaks all database fields including sensitive ones
}
// Frontend using Prisma types
import type { User } from "@prisma/client";
function UserProfile({ user }: { user: User }) {
// Component now coupled to database schema
}
Correct (explicit DTOs):
// Define explicit DTOs
interface UserDTO {
id: number;
name: string;
email: string;
// Only fields needed by the client
}
// API route transforms to DTO
export async function GET(): Promise<UserDTO> {
const user = await userRepository.findById(id);
return UserResponseSchema.parse(user); // Validate with Zod
}
// Frontend uses DTO
function UserProfile({ user }: { user: UserDTO }) {
// Component decoupled from database
}
The standard:
- Data layer → Application layer → API: Transform database models into application-layer DTOs, then transform application DTOs into API-specific DTOs
- API → Application layer → Data layer: Transform API DTOs through application layer and into data-specific DTOs
- All DTO conversions through Zod to ensure all data is validated before sending to user
DTO Location and Naming
Location: All DTOs go in packages/lib/dto/
Naming conventions:
- Base entity:
{Entity}Dto(e.g.,BookingDto) - With relations:
{Entity}With{Relations}Dto(e.g.,BookingWithAttendeesDto) - For specific projections:
{Entity}For{Purpose}Dto(e.g.,BookingForConfirmationDto) - Avoid:
{Entity}Dto2,{Entity}DtoForHandler, or other use-case-specific names
Enum/union pattern - use string literal unions to stay ORM-agnostic:
// Good - ORM-agnostic string literal union
export type BookingStatusDto = "CANCELLED" | "ACCEPTED" | "REJECTED" | "PENDING";
// Bad - importing Prisma enum
import { BookingStatus } from "@calcom/prisma/client";
Type safety - never use as any in DTO mapping functions. If types don't align, fix the mapping explicitly.
Prisma Boundaries
- Allowed:
packages/prisma, repository implementations (packages/features/**/repositories/*Repository.ts), and low-level data access infrastructure. - Not allowed:
packages/features/**business logic (non-repository),packages/trpc/**handlers,apps/web/**,apps/api/v2/**services/controllers, and workflow/webhook/service layers.
Yes, this requires more code. Yes, it's worth it. Explicit boundaries prevent the architectural erosion that creates long-term maintenance nightmares.
Reference: Cal.com Engineering Blog