Files
calendar/agents/rules/data-dto-boundaries.md
T
Keith WilliamsGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
d4ea931bf8 feat(agents): add modular engineering rules from 2026 standards (#26847)
* 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>
2026-01-16 10:57:28 +01:00

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:

  1. Data layer → Application layer → API: Transform database models into application-layer DTOs, then transform application DTOs into API-specific DTOs
  2. API → Application layer → Data layer: Transform API DTOs through application layer and into data-specific DTOs
  3. 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