Claude Code Plugins

Community-maintained marketplace

Feedback

development-guidelines

@jaccurran/pipedriven
1
0

Reference for Dockside/Riza architecture patterns, TDD methodology, coding standards, data serialization, and code reuse practices. Use when creating new files, writing tests, making architectural decisions, or optimizing UI performance. Promotes Context7 usage for best practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name development-guidelines
description Reference for Dockside/Riza architecture patterns, TDD methodology, coding standards, data serialization, and code reuse practices. Use when creating new files, writing tests, making architectural decisions, or optimizing UI performance. Promotes Context7 usage for best practices.
allowed-tools Read, Grep, Glob, mcp__context7__resolve-library-id, mcp__context7__query-docs

Development Guidelines

Comprehensive reference for Test-Driven Development (TDD), data serialization best practices, architectural patterns, coding standards, and code reuse for the Dockside/Riza sailing club management system.

Core Philosophy: Test-first development with maximum code reuse, optimized data serialization, and library best practices via Context7.

When to Use This Skill

Automatically reference these guidelines when:

  • Starting ANY new feature or bug fix (TDD required)
  • Fetching data from database (optimize serialization)
  • Passing props to components (check prop size)
  • Creating new files or components
  • Writing or updating tests
  • Making architectural decisions
  • Implementing RBAC or authentication
  • Working with Prisma/database
  • Using a library pattern for the first time (check Context7)
  • Unsure of best practice (query Context7)

๐Ÿ“š Context7: Always Check Best Practices First

Critical Rule: Query Context7 Before Implementing

BEFORE writing code with ANY library, check Context7 for current best practices.

When to Use Context7

โœ… Always use Context7 when:

  • Using a Next.js pattern for the first time
  • Implementing React hooks or patterns
  • Writing Prisma queries
  • Setting up authentication flows
  • Implementing form validation
  • Using Tailwind patterns
  • Working with any npm library
  • Unsure if your approach is optimal

How to Use Context7

// Step 1: Resolve library ID (if not known)
const libs = await resolveLibraryId("next.js", "server components data fetching")

// Step 2: Query for best practices
const docs = await queryDocs("/vercel/next.js", "server component data fetching patterns")

// Step 3: Apply recommended patterns to your code

Common Queries by Feature Area

Working on... Library ID Example Query
Server Components /vercel/next.js "server component data fetching patterns"
API Routes /vercel/next.js "app router api route best practices"
React Hooks /facebook/react "useEffect cleanup function patterns"
Form Handling /react-hook-form/react-hook-form "form validation with zod schema"
Prisma Queries /prisma/prisma "query optimization with select and include"
Prisma Transactions /prisma/prisma "transaction best practices error handling"
Authentication /nextauthjs/next-auth "session management patterns"
Tailwind Components /tailwindlabs/tailwindcss "responsive design patterns"
Testing React /testing-library/react-testing-library "testing async components"
Vitest Mocking /vitest-dev/vitest "mocking modules best practices"

Context7 Integration in Workflow

Development Flow with Context7:

1. Understand requirement
2. ๐Ÿ” Query Context7 for best practice
3. Apply recommended pattern
4. Write test (TDD)
5. Implement using best practice
6. Verify against Context7 recommendation

Example: Server Component Data Fetching

// โŒ BEFORE Context7: Might use outdated pattern
export default async function Page() {
  const data = await fetch('/api/data').then(r => r.json())
  return <div>{data.value}</div>
}

// โœ… AFTER Context7 query: "next.js server component data fetching"
// Learned: Use Prisma directly, optimize with select
export default async function Page() {
  const data = await prisma.model.findMany({
    select: {
      id: true,
      name: true,
      // Only fetch what's needed
    }
  })
  return <div>{data.map(...)}</div>
}

Example: React Hook Pattern

// โŒ BEFORE Context7: Might miss cleanup
useEffect(() => {
  fetchData()
}, [dependency])

// โœ… AFTER Context7 query: "react useEffect cleanup patterns"
// Learned: Always clean up async operations
useEffect(() => {
  let cancelled = false

  async function fetchData() {
    const result = await api.getData()
    if (!cancelled) {
      setData(result)
    }
  }

  fetchData()

  return () => {
    cancelled = true
  }
}, [dependency])

Example: Prisma Query Optimization

// โŒ BEFORE Context7: N+1 query problem
const events = await prisma.event.findMany()
for (const event of events) {
  event.club = await prisma.club.findUnique({ where: { id: event.clubId }})
}

// โœ… AFTER Context7 query: "prisma query optimization select include"
// Learned: Use include for relations, select for specific fields
const events = await prisma.event.findMany({
  select: {
    id: true,
    name: true,
    club: {
      select: {
        id: true,
        name: true,
      }
    }
  }
})

Context7 Best Practice Checklist

Before implementing any pattern:

  • Query Context7 for current best practices
  • Compare recommended pattern with your approach
  • Apply best practice if different
  • Document why you chose a pattern (if deviating from Context7)
  • Update your knowledge of library patterns

Proactive Context7 Usage

Make it a habit:

  • ๐Ÿ” "Let me check Context7 for the latest pattern..."
  • ๐Ÿ” "Is this the recommended way? Let me verify..."
  • ๐Ÿ” "Before I implement, let me see what Context7 says..."

Remember: Library best practices evolve. Context7 ensures you're using current, recommended patterns, not outdated approaches.


โšก Data Serialization & UI Performance (CRITICAL)

The 100MB Prop Problem

Issue: Passing large database objects (100MB) to UI components causes:

  • Extreme slowdown during serialization
  • Client-side memory bloat
  • Poor user experience
  • Network transfer overhead

Solution: Fetch only what you need, transform close to the source.

Data Serialization Rules (7 Essential)

# Rule Example Impact
1 โœ… Use select to fetch only needed fields select: { id, name, email } 90%+ size reduction
2 โœ… Transform data in server components Filter/map before passing to client No client-side overhead
3 โœ… Use pagination for large datasets Limit queries to 50-100 records Constant memory usage
4 โŒ NEVER pass entire database models Don't pass raw Prisma results Avoid nested relations
5 โœ… Use include strategically Only include what UI needs Control relation depth
6 โœ… Create view models for UI Map DB โ†’ UI-specific types Clean separation
7 โœ… Monitor prop sizes in dev Log prop size if user is debug user Early detection

Pattern: Optimized Data Fetching

// โŒ BAD: Fetching everything (100MB+)
const event = await prisma.event.findUnique({
  where: { id },
  include: {
    club: true,
    raceDays: {
      include: {
        registrations: {
          include: {
            sailor: {
              include: {
                user: true,
                boats: true,
                results: true, // ๐Ÿšจ HUGE!
              }
            }
          }
        },
        races: {
          include: {
            results: true, // ๐Ÿšจ MASSIVE!
          }
        }
      }
    },
    classes: {
      include: {
        class: true
      }
    }
  }
})

// Passing 100MB+ to client component ๐Ÿ’ฅ
return <EventDetails event={event} />
// โœ… GOOD: Fetch only what UI needs (90KB)
const event = await prisma.event.findUnique({
  where: { id },
  select: {
    id: true,
    name: true,
    type: true,
    startDate: true,
    endDate: true,
    club: {
      select: {
        id: true,
        name: true,
        timezone: true,
      }
    },
    raceDays: {
      select: {
        id: true,
        date: true,
        status: true,
        _count: {
          select: {
            registrations: true, // Just count, not full data
          }
        }
      },
      take: 10, // Paginate if needed
    },
    _count: {
      select: {
        registrations: true,
        raceDays: true,
      }
    }
  }
})

// Transform to view model
const eventViewModel = {
  id: event.id,
  name: event.name,
  type: event.type,
  dateRange: `${event.startDate} - ${event.endDate}`,
  club: event.club,
  upcomingRaceDays: event.raceDays.filter(rd => rd.status === 'PLANNED'),
  stats: {
    totalRegistrations: event._count.registrations,
    totalRaceDays: event._count.raceDays,
  }
}

// Passing ~90KB to client component โœ…
return <EventDetails event={eventViewModel} />

Pattern: Server Component Data Transformation

Use server components to filter and transform data close to the source:

// app/events/[id]/page.tsx - Server Component
export default async function EventPage({ params }) {
  const { id } = await params

  // Fetch optimized data
  const eventData = await prisma.event.findUnique({
    where: { id },
    select: {
      // Only fields needed for this page
      id: true,
      name: true,
      description: true,
      startDate: true,
      endDate: true,
      club: {
        select: {
          id: true,
          name: true,
          logo: true,
        }
      },
      // Use _count for aggregates
      _count: {
        select: {
          registrations: true,
          raceDays: true,
        }
      }
    }
  })

  // Transform to view model in server component
  const viewModel = {
    id: eventData.id,
    title: eventData.name,
    summary: eventData.description?.substring(0, 200),
    dates: {
      start: eventData.startDate,
      end: eventData.endDate,
      formatted: formatDateRange(eventData.startDate, eventData.endDate)
    },
    club: eventData.club,
    metrics: {
      participants: eventData._count.registrations,
      raceDays: eventData._count.raceDays,
    }
  }

  // Pass optimized, transformed data to client component
  return <EventDetailsClient event={viewModel} />
}

Pattern: Pagination and Infinite Scroll

// โœ… GOOD: Paginated data fetching
const getEvents = async (page: number = 1, pageSize: number = 50) => {
  const skip = (page - 1) * pageSize

  const [events, total] = await Promise.all([
    prisma.event.findMany({
      select: {
        id: true,
        name: true,
        startDate: true,
        club: {
          select: { name: true }
        }
      },
      skip,
      take: pageSize,
      orderBy: { startDate: 'desc' }
    }),
    prisma.event.count() // Separate count query
  ])

  return {
    events,
    pagination: {
      page,
      pageSize,
      total,
      totalPages: Math.ceil(total / pageSize),
      hasMore: skip + pageSize < total
    }
  }
}

Pattern: Use _count for Aggregates

Don't fetch relations just to count them:

// โŒ BAD: Fetching all registrations just to count
const event = await prisma.event.findUnique({
  where: { id },
  include: {
    registrations: true, // Could be thousands!
  }
})
const count = event.registrations.length // ๐Ÿ’ฅ Huge memory usage

// โœ… GOOD: Use _count
const event = await prisma.event.findUnique({
  where: { id },
  select: {
    id: true,
    name: true,
    _count: {
      select: {
        registrations: true, // Just returns number
      }
    }
  }
})
const count = event._count.registrations // โœ… Efficient

Pattern: View Models for UI

Create dedicated view model types for UI components:

// types/event.ts

// โŒ Don't use raw Prisma types in UI
import type { Event } from '@prisma/client/index'

// โœ… Create UI-specific view models
export interface EventListItemViewModel {
  id: string
  name: string
  dates: {
    start: Date
    end: Date
    formatted: string
  }
  club: {
    id: string
    name: string
  }
  stats: {
    participants: number
    raceDays: number
  }
  status: 'upcoming' | 'ongoing' | 'completed'
}

export interface EventDetailsViewModel extends EventListItemViewModel {
  description: string
  location: string
  upcomingRaceDays: {
    id: string
    date: Date
    status: string
  }[]
}

// services/eventViewModelMapper.ts
export function mapToEventListItem(event: PrismaEventResult): EventListItemViewModel {
  return {
    id: event.id,
    name: event.name,
    dates: {
      start: event.startDate,
      end: event.endDate,
      formatted: formatDateRange(event.startDate, event.endDate)
    },
    club: {
      id: event.club.id,
      name: event.club.name
    },
    stats: {
      participants: event._count.registrations,
      raceDays: event._count.raceDays
    },
    status: determineEventStatus(event.startDate, event.endDate)
  }
}

Debug Logging for Prop Sizes

Monitor prop sizes in development:

// components/EventDetails.tsx
'use client'

export function EventDetails({ event }: { event: EventViewModel }) {
  // Debug logging for development
  if (process.env.NODE_ENV === 'development') {
    const propSize = JSON.stringify(event).length
    const propSizeKB = (propSize / 1024).toFixed(2)

    console.log('[EventDetails] Prop size:', `${propSizeKB} KB`)

    // Warn if prop is large
    if (propSize > 100_000) { // 100KB warning threshold
      console.warn('โš ๏ธ  Large prop detected:', `${propSizeKB} KB`)
    }
  }

  return <div>...</div>
}

Special Debug User Logging

For jac.curran@gmail.com, add prop size logging:

// app/events/[id]/page.tsx
export default async function EventPage({ params }) {
  const session = await getServerSession()
  const eventData = await fetchEventData(id)

  // Debug logging for specific user
  if (session?.user?.email === 'jac.curran@gmail.com') {
    const dataSize = JSON.stringify(eventData).length
    const dataSizeKB = (dataSize / 1024).toFixed(2)

    console.log('[DEBUG] Event data size:', `${dataSizeKB} KB`)
    console.log('[DEBUG] Event data keys:', Object.keys(eventData))
  }

  return <EventDetailsClient event={eventData} />
}

Performance Checklist

Before fetching data:

  • Use select to fetch only needed fields
  • Use _count for aggregates, not full relations
  • Use take and skip for pagination
  • Transform data in server components before passing to client
  • Create view models for UI-specific data shapes
  • Monitor prop sizes in development
  • Avoid deep nested includes (max 2-3 levels)
  • Consider caching for frequently accessed data

๐Ÿงช Test-Driven Development (TDD) - MANDATORY

TDD Development Flow (Follow for ALL New Code)

CRITICAL: Always write tests BEFORE writing implementation code.

1. **Check Context7**: Query for best practices on pattern you're implementing
2. **Analyze First**: Study dependencies, business logic, and existing patterns
3. **Write Test**: Create failing tests that describe desired behavior
4. **Write Code**: Implement minimum code to make tests pass (using Context7 patterns)
5. **Refactor**: Improve code while keeping tests green
6. **Repeat**: Continue cycle for each feature increment

Essential TDD Checklist

Before writing ANY code:

  • Analyze dependencies and business rules
  • Read the implementation first (for modifications) to understand actual behavior
  • Check existing utilities in __tests__/utils/ for reusable test code
  • Use centralized factories - NEVER create inline test data
  • Mock exactly what the code uses (check imports and method calls)
  • Use complete, realistic test data with future dates
  • Test business behavior, not implementation details
  • Ensure tests are isolated and repeatable
  • Follow Vitest hoisting rules for mocks

๐Ÿญ Code Reuse: Centralized Testing Utilities

Critical Rule: Always Check Existing Utilities FIRST

Before writing ANY test code, check __tests__/utils/ for:

  1. testDataFactories.ts - Mock data factories for unit tests

    import { createMockEvent, createMockUser } from '@utils/testDataFactories'
    
    const event = createMockEvent({ name: 'Custom Name' })
    
  2. testDatabaseHelpers.ts - Database setup for integration tests

    import { setupTestDatabase, createTestEvent } from '@utils/testDatabaseHelpers'
    
    const context = await setupTestDatabase()
    const eventId = await createTestEvent(context, { name: 'Test Event' })
    
  3. testCleanupHelpers.ts - Cleanup helpers (respects foreign keys)

    import { cleanupTestContext } from '@utils/testCleanupHelpers'
    
    await cleanupTestContext(context)
    
  4. mockManagementUtilities.ts - Mock helpers

    import { mockAuthSession, resetAllMocks } from '@utils/mockManagementUtilities'
    
  5. authTestHelpers.ts - Auth mocking helpers

    import { createAuthContext, clearAuthMocks } from '@utils/authTestHelpers'
    

Code Reuse Benefits

  • Schema changes: Update 1 file instead of 50+ files
  • Test writing speed: 50% faster with factories
  • File size: 50-70% smaller test files
  • Consistency: Same patterns across all tests
  • Maintainability: Single source of truth

Code Reuse Rules (7 Essential)

# Rule Impact
1 โœ… ALWAYS use centralized factories 80% less schema change impact
2 โŒ NEVER create inline Prisma structures 50-70% smaller test files
3 โœ… ALWAYS use cleanup helpers 100% correct foreign key handling
4 โœ… Reuse test contexts across related tests 30% faster test execution
5 โœ… Use factory composition for complex setups 50% faster test writing
6 โœ… Keep test files < 300 lines Better maintainability
7 โœ… Extract setup to beforeEach Reduced duplication

Quick Decision Trees

Should I check Context7 first?

About to implement a library pattern?
โ”œโ”€ Using it for the first time?
โ”‚  โ””โ”€ Query Context7 FIRST โœ…
โ”‚     โ””โ”€ "/library-id" + "pattern description"
โ”‚
โ”œโ”€ Unsure if it's the best approach?
โ”‚  โ””โ”€ Query Context7 to verify โœ…
โ”‚     โ””โ”€ Compare your approach to recommended
โ”‚
โ”œโ”€ Getting unexpected behavior?
โ”‚  โ””โ”€ Query Context7 for correct usage โœ…
โ”‚     โ””โ”€ Check if you're using deprecated pattern
โ”‚
โ””โ”€ Already confident in the pattern?
   โ””โ”€ Proceed (but spot-check Context7 occasionally)

Should I optimize this query?

Fetching data from database?
โ”œโ”€ ๐Ÿ” First: Query Context7 "prisma query optimization"
โ”‚
โ”œโ”€ Count only?
โ”‚  โ””โ”€ Use _count โœ…
โ”‚
โ”œโ”€ List view (many records)?
โ”‚  โ””โ”€ Use select + pagination โœ…
โ”‚
โ”œโ”€ Detail view (single record)?
โ”‚  โ””โ”€ Use select with strategic include โœ…
โ”‚     โ””โ”€ Max 2-3 levels deep
โ”‚
โ””โ”€ Passing to client component?
   โ””โ”€ Transform to view model FIRST โœ…
      โ””โ”€ Monitor prop size in dev

Should I write tests first?

Are you writing code?
โ””โ”€ Yes โ†’ Write tests FIRST (TDD required)
   โ”œโ”€ New feature
   โ”‚  โ””โ”€ ๐Ÿ” Query Context7 for testing pattern
   โ”‚     โ””โ”€ Analyze โ†’ Write test โ†’ Implement
   โ”‚
   โ”œโ”€ Bug fix
   โ”‚  โ””โ”€ Write regression test โ†’ Fix bug
   โ”‚
   โ””โ”€ Refactor
      โ””โ”€ Tests already exist โ†’ Refactor โ†’ Tests still pass

Which test utility should I use?

Need test data?
โ”œโ”€ Unit test (mocked Prisma)?
โ”‚  โ””โ”€ Use testDataFactories.ts โœ…
โ”‚     โ””โ”€ createMockEvent(), createMockUser(), etc.
โ”‚
โ”œโ”€ Integration test (real database)?
โ”‚  โ””โ”€ Use testDatabaseHelpers.ts โœ…
โ”‚     โ””โ”€ setupTestDatabase(), createTestEvent(), etc.
โ”‚
โ””โ”€ Need cleanup?
   โ””โ”€ Use testCleanupHelpers.ts โœ…
      โ””โ”€ cleanupTestContext(), cleanupEvent(), etc.

Should this be a Server or Client Component?

Does it need user interaction (clicks, forms, state)?
โ”œโ”€ No โ†’ Server Component โœ…
โ”‚  โ””โ”€ Fetch & transform data here
โ”‚     โ””โ”€ Pass optimized props to client
โ”‚
โ””โ”€ Yes
   โ””โ”€ Can you use form actions instead?
      โ”œโ”€ Yes โ†’ Server Component + Form Actions โœ…
      โ””โ”€ No โ†’ Client Component ("use client")
         โ””โ”€ Receive pre-optimized props from server

Where does this code belong?

Business Logic โ†’ /server/services
Data Access (Prisma) โ†’ /server/services (with select optimization)
Data Transformation โ†’ Server components or /lib/mappers
UI Component โ†’ /components (client)
Page/Route โ†’ /app (server)
API Endpoint โ†’ /app/api
Type Definition โ†’ /types (view models + DB types)
Server Utility โ†’ /lib or /server/utils
Test โ†’ /__tests__

Architecture Patterns

Core Principles

  1. Best Practices First: Query Context7 before implementing library patterns
  2. TDD-First: Tests before code, always
  3. Data Optimization: Fetch only what you need, transform early
  4. Code Reuse: Use centralized utilities, never duplicate
  5. Server-First: Default to server components
  6. Service Layer: All business logic lives in /server/services
  7. Single Responsibility: Each module has one clear purpose
  8. Type Safety: TypeScript strict mode throughout
  9. Security by Default: RBAC checks, input validation, audit logging

Technology Stack

  • Framework: Next.js 14+ (App Router)
  • Database: PostgreSQL via Prisma ORM
  • Authentication: NextAuth.js
  • Validation: Zod
  • Styling: Tailwind CSS (no inline styles)
  • Testing: Vitest + React Testing Library + Playwright

File Organization

Directory Structure

/app                    # Next.js App Router (routes and server components)
  /(auth)              # Authentication routes
  /api                 # API route handlers
  /[club-slug]         # Dynamic routes for clubs

/components            # Reusable React components
  /ui                  # Base UI components (shadcn/ui)
  /forms               # Form components
  /[feature]           # Feature-specific components

/server                # Server-side business logic
  /services            # Business logic services (ALL PRISMA HERE)

/lib                   # Shared utilities and configurations
  /auth.ts             # Authentication utilities
  /prisma.ts           # Prisma client singleton
  /mappers             # Data โ†’ View Model mappers

/types                 # TypeScript type definitions
  /viewModels          # UI-specific view model types

/prisma                # Database schema and migrations
  schema.prisma        # Prisma schema

/__tests__             # Test files
  /utils               # Centralized test utilities โญ
    testDataFactories.ts
    testDatabaseHelpers.ts
    testCleanupHelpers.ts
    mockManagementUtilities.ts
  /services            # Service unit tests
  /components          # Component tests
  /integration         # Integration tests
  /e2e                 # End-to-end tests

File Naming Conventions

  • Pages: page.tsx (Next.js convention)
  • API Routes: route.ts (Next.js convention)
  • Components: ComponentName.tsx (PascalCase)
  • Services: serviceNameService.ts (camelCase + Service suffix)
  • Utilities: utilityName.ts (camelCase)
  • Types: domainName.ts (camelCase)
  • View Models: featureViewModel.ts (camelCase + ViewModel suffix)
  • Tests: *.test.ts or *.spec.ts
  • Custom Hooks: use[A-Z]*.ts (use prefix)

File Length Limits

  • Maximum file length: 600 lines
  • Maximum component length: 300 lines
  • Maximum test file: 300 lines (use centralized factories!)
  • If exceeded, refactor into smaller modules

Import Organization (STRICT)

Import Order - Must Follow Exactly

Group imports in this exact order with newlines between groups:

  1. Third-party imports (React, Next.js, external libraries)
  2. Local imports (@/ absolute imports)
  3. Component imports (relative imports ./)
  4. Type imports (always use import type)

Within each group: Sort alphabetically

Import Rules

  • โœ… Always use import type for TypeScript types
  • โœ… Use absolute imports (@/) for local modules
  • โœ… Use relative imports (./) for component-specific files only
  • โœ… No duplicate imports
  • โœ… No unused imports
  • โŒ Never use require() - always use import

Correct Import Example

// 1. Third-party imports
import { useState, useEffect } from 'react'
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'

// 2. Local imports (@/)
import { prisma } from '@/lib/prisma'
import { hasRole } from '@/lib/auth'
import { mapToEventViewModel } from '@/lib/mappers/eventMapper'
import { Button } from '@/components/ui/button'

// 3. Component imports (./)
import { EventCard } from './EventCard'
import { EventList } from './EventList'

// 4. Type imports
import type { Prisma } from '@prisma/client/index'
import type { Event, Club } from '@prisma/client/index'
import type { EventViewModel } from '@/types/viewModels/eventViewModel'

Component Architecture

Server Components (Default)

Use server components by default for:

  • Data fetching from database (with optimized select)
  • Data transformation (DB โ†’ View Models)
  • Server-side business logic
  • No user interactivity required

Server Component Rules:

  • โœ… Default choice - no "use client" directive
  • โœ… Direct database access via services
  • โœ… Async functions allowed
  • โœ… Transform data here before passing to client
  • โœ… Use select to minimize data fetched
  • โŒ No useState, useEffect, or event handlers
  • โŒ No browser APIs

Client Components

Use client components when you need:

  • User interactivity (clicks, form inputs)
  • Browser APIs (localStorage, window)
  • React hooks (useState, useEffect)
  • Real-time updates

Client Component Rules:

  • โœ… Mark with "use client" directive at top of file
  • โœ… Keep components small and focused
  • โœ… Receive optimized view models via props (not raw DB data)
  • โœ… Use form actions for mutations
  • โœ… Monitor prop sizes in development
  • โŒ No direct database access
  • โŒ No direct Prisma calls
  • โŒ No server-side secrets

Service Layer Pattern

Service Responsibilities

ALL business logic must reside in /server/services

Services handle:

  • Business logic and validation
  • Database operations via Prisma (with optimized select)
  • Transaction management
  • Error handling
  • Audit logging
  • Email notifications
  • Data transformations

Service Structure (TDD Pattern with Data Optimization)

// 1. WRITE TEST FIRST โญ
// __tests__/services/eventService.test.ts

import { createMockEvent } from '@utils/testDataFactories'

describe('EventService', () => {
  it('should create event successfully', async () => {
    // Arrange
    const input = createMockEvent({ name: 'Test Event' })

    // Act
    const result = await createEvent(input, userId)

    // Assert
    expect(result.success).toBe(true)
    expect(result.data.name).toBe('Test Event')
  })
})

// 2. IMPLEMENT SERVICE WITH DATA OPTIMIZATION โญ
// server/services/eventService.ts

import type { Prisma } from '@prisma/client/index'
import { prisma } from '@/lib/prisma'
import { hasRole } from '@/lib/auth'
import { logAuditAction } from '@/server/services/auditLogService'
import { z } from 'zod'

// Input validation schemas
const createEventSchema = z.object({
  name: z.string().min(1).max(200),
  type: z.enum(['SERIES', 'SINGLE']),
  clubId: z.string().uuid(),
})

// Return type definitions
interface ServiceResult<T> {
  success: boolean
  data?: T
  error?: string
}

// Service functions with optimized queries
export async function createEvent(
  input: z.infer<typeof createEventSchema>,
  userId: string
): Promise<ServiceResult<Event>> {
  try {
    // Validate input
    const validatedInput = createEventSchema.parse(input)

    // Check permissions
    const canCreate = await hasRole(userId, validatedInput.clubId, 'EVENT_MANAGER')
    if (!canCreate) {
      return { success: false, error: 'Insufficient permissions' }
    }

    // Business logic with optimized select
    const event = await prisma.event.create({
      data: {
        ...validatedInput,
        createdById: userId,
      },
      select: {
        id: true,
        name: true,
        type: true,
        startDate: true,
        endDate: true,
        club: {
          select: {
            id: true,
            name: true,
            timezone: true,
          }
        },
        _count: {
          select: {
            raceDays: true,
            registrations: true,
          }
        }
      }
    })

    // Audit logging
    await logAuditAction({
      userId,
      action: 'CREATE',
      resourceType: 'EVENT',
      resourceId: event.id,
      details: { eventName: event.name }
    })

    return { success: true, data: event }

  } catch (error) {
    console.error('Error creating event:', error)
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Failed to create event'
    }
  }
}

Service Rules

  • โœ… Use select to fetch only needed fields
  • โœ… Use _count for aggregates, not full relations
  • โœ… All Prisma operations in try/catch blocks
  • โœ… Use Zod for input validation
  • โœ… Return structured result objects { success, data, error }
  • โœ… Log all mutations to audit log
  • โœ… Check permissions before operations
  • โœ… Use transactions for multi-step operations
  • โœ… Optimize queries (strategic include and select)
  • โŒ Never expose raw errors to client
  • โŒ No business logic in API routes or components
  • โŒ No direct Prisma calls outside services
  • โŒ Never fetch more data than needed

Prisma & Database Patterns

Prisma Type Imports (CRITICAL)

Always use type imports from @prisma/client/index:

// โœ… CORRECT
import type { Prisma } from '@prisma/client/index'
import type { Event, Club, Registration } from '@prisma/client/index'
import { prisma } from '@/lib/prisma'

// โŒ INCORRECT
import { Prisma, Event } from '@prisma/client/index'  // Not type import
import { prisma } from '@prisma/client'  // Wrong path

Prevent N+1 Queries

// โœ… GOOD: Single query with includes
const events = await prisma.event.findMany({
  select: {
    id: true,
    name: true,
    startDate: true,
    club: {
      select: {
        id: true,
        name: true,
      }
    },
    _count: {
      select: {
        registrations: true, // Just count
      }
    }
  },
  take: 50, // Always paginate
})

// โŒ BAD: N+1 query problem
const events = await prisma.event.findMany()
for (const event of events) {
  event.club = await prisma.club.findUnique({ where: { id: event.clubId }})
}

Type System

TypeScript Rules

  • โœ… All functions have explicit return types
  • โœ… All parameters have explicit types
  • โœ… No explicit any types
  • โœ… No implicit any types
  • โœ… Use type imports for Prisma types
  • โœ… Create view model types for UI props

Type Definitions

Use interface for object types:

interface User {
  id: string
  name: string
  email: string
}

Use type for union/intersection types:

type EventStatus = 'DRAFT' | 'PUBLISHED' | 'COMPLETED'
type EventWithClub = Event & { club: Club }

Use Prisma's generated types:

type EventWithClub = Prisma.EventGetPayload<{
  include: { club: true }
}>

Create View Models for UI:

// types/viewModels/eventViewModel.ts
export interface EventListItemViewModel {
  id: string
  name: string
  dateRange: string
  club: {
    id: string
    name: string
  }
  stats: {
    participants: number
    raceDays: number
  }
}

Naming Conventions

Code Naming

  • React Components: PascalCase (EventCard, UserProfile)
  • Functions/Variables: camelCase (getEventById, userName)
  • Constants: UPPER_SNAKE_CASE (MAX_RETRIES, API_BASE_URL)
  • Types/Interfaces: PascalCase (EventFormInput, ServiceResult)
  • View Models: PascalCase with ViewModel suffix (EventListItemViewModel)
  • Enums: PascalCase (EventStatus, UserRole)

Performance Patterns

Database Query Optimization (CRITICAL)

  1. Use select for Specific Fields - Always prefer select over full model fetch
  2. Use _count for Aggregates - Don't fetch relations just to count
  3. Use take and skip for Pagination - Never fetch all records
  4. Batch Queries with Promise.all - Parallel queries when possible
  5. Monitor Query Performance - Log slow queries in development

Component Performance

Code Splitting:

import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(
  () => import('@/components/HeavyComponent'),
  { loading: () => <div>Loading...</div> }
)

Image Optimization:

import Image from 'next/image'

<Image
  src="/logo.png"
  alt="Logo"
  width={200}
  height={100}
  priority
/>

Special User Debug Logging

When user email is jac.curran@gmail.com, add debug logging:

if (session?.user?.email === 'jac.curran@gmail.com') {
  console.log('[DEBUG]', {
    context: 'eventCreation',
    dataSize: `${(JSON.stringify(data).length / 1024).toFixed(2)} KB`,
    data: eventData
  })
}

Quick Reference Checklist

Before creating any new code, verify:

Context7 Best Practices Checklist

  • Queried Context7 for library-specific best practices
  • Compared my approach with Context7 recommendations
  • Using current patterns (not deprecated/outdated)
  • Verified with Context7 if unsure about implementation

Data Optimization Checklist

  • Checked Context7 "prisma query optimization" patterns
  • Used select to fetch only needed fields
  • Used _count for aggregates (not full relations)
  • Used pagination (take and skip) for lists
  • Transformed data in server component before passing to client
  • Created view model type for UI props
  • Monitored prop size in development
  • Avoided deep nested includes (max 2-3 levels)

TDD Checklist

  • Queried Context7 for testing patterns (if needed)
  • Analyzed dependencies and business rules FIRST
  • Checked __tests__/utils/ for reusable utilities
  • Written tests before writing implementation code
  • Used centralized factories (no inline test data)
  • Tests pass and are isolated

Code Quality Checklist

  • Verified with Context7 if using library pattern for first time
  • Imports organized correctly (third-party โ†’ local โ†’ component โ†’ types)
  • Using server component unless interactivity needed
  • Business logic in /server/services
  • Prisma types imported with import type from @prisma/client/index
  • RBAC checks in place for protected operations
  • Input validation with Zod
  • Proper error handling with try/catch
  • Audit logging for sensitive operations
  • TypeScript types explicitly defined
  • Following naming conventions (PascalCase, camelCase, etc.)
  • Tailwind CSS only (no inline styles)
  • File under 600 lines (300 for components, tests)

Documentation References

For detailed testing patterns, templates, and examples, see:

  • documentation/testing/TESTING-DOC.md - Main testing guide
  • documentation/testing/TESTING_STRATEGY.md - Testing philosophy
  • documentation/testing/TESTING_PATTERNS.md - Detailed patterns
  • documentation/testing/TESTING_TEMPLATES.md - Reusable templates

Remember:

  • Context7 first - Always check best practices before implementing
  • TDD-first development with maximum code reuse
  • Data optimization at the source (use select, transform early)
  • View models for UI components (not raw DB models)
  • Monitor performance in development (prop sizes, query times)
  • Tests are documentation of how the system actually works

When in doubt:

  1. Query Context7 for best practices
  2. Write a test first
  3. Use centralized utilities
  4. Optimize data fetching
  5. Follow existing patterns