patterns-dependency-injection
**Impact: HIGH**
$ npx -y skills add calcom/cal.com --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
**Impact: HIGH**
Agent definition
patterns-dependency-injection.mdtitle: Use Dependency Injection for Loose Coupling
impact: HIGH
impactDescription: Enables build-time safety, testability, and maintainability
tags: patterns, dependency-injection, di, ioctopus, moduleloader, testing, coupling
Use Dependency Injection for Loose Coupling
**Impact: HIGH**
Dependency Injection enables loose coupling, facilitates testing, and isolates concerns. Dependencies should be injected via DI containers rather than instantiated directly within classes.
**Incorrect (tight coupling with direct instantiation):**
class BookingService {
private repository = new BookingRepository();
private emailService = new EmailService();
private calendarService = new GoogleCalendarService();
async createBooking(data: CreateBookingDTO) {
const booking = await this.repository.create(data);
await this.emailService.sendConfirmation(booking);
await this.calendarService.createEvent(booking);
return booking;
}
}**Correct (dependency injection):**
class BookingService {
constructor(
private readonly repository: BookingRepository,
private readonly emailService: EmailService,
private readonly calendarService: CalendarService,
) {}
async createBooking(data: CreateBookingDTO) {
const booking = await this.repository.create(data);
await this.emailService.sendConfirmation(booking);
await this.calendarService.createEvent(booking);
return booking;
}
}**Required patterns:**
- **Application Services**: Orchestrate use cases, coordinate between domain services and repositories
- **Domain Services**: Contain business logic that doesn't naturally belong to a single entity
- **Repositories**: Abstract data access, isolate technology choices
- **Caching Proxies**: Wrap repositories or services to add caching behavior transparently
- **Decorators**: Add cross-cutting concerns (logging, metrics) without polluting domain logic
Cal.diy's Type-Safe DI with moduleLoader
We use `@evyweb/ioctopus` to manage service and repository dependencies. The **moduleLoader pattern** provides type-safe dependency injection, ensuring that if a service adds a new dependency, TypeScript will catch missing dependencies at build time rather than runtime.
Core Concepts
**Tokens**: Unique symbols that identify each service or repository in the DI container. Every injectable class needs a corresponding token. Tokens should be defined in a `tokens.ts` file within the feature's `di/` directory.
**Modules** (`.module.ts` files): Define how classes are instantiated and what dependencies they require using the `bindModuleToClassOnToken` function. Each module exports a `moduleLoader` object that knows how to load itself and its dependencies.
**Containers** (`.container.ts` files): Create a container instance and expose getter functions that consumers use to obtain service instances. Containers use the moduleLoader to automatically load all required dependencies.
**`bindModuleToClassOnToken`**: A type-safe function that binds a class to a token and declares its dependencies. TypeScript ensures the declared dependencies match what the class constructor expects.
How It Works
**Step 1: Create tokens in the feature's di directory**
// packages/features/myfeature/di/tokens.ts
export const MY_FEATURE_DI_TOKENS = {
MY_SERVICE: Symbol("MyService"),
MY_SERVICE_MODULE: Symbol("MyServiceModule"),
};Then import these tokens in the central tokens file:
// packages/features/di/tokens.ts
import { MY_FEATURE_DI_TOKENS } from "@calcom/features/myfeature/di/tokens";
export const DI_TOKENS = {
// ...existing tokens
...MY_FEATURE_DI_TOKENS,
};**Step 2: Define the service class with constructor injection**
For services with multiple dependencies, use a dependencies interface:
// packages/features/myfeature/services/MyService.ts
export interface IMyServiceDeps {
bookingRepo: BookingRepository;
userRepo: UserRepository;
}
export class MyService {
constructor(private deps: IMyServiceDeps) {}
async doSomething() {
const bookings = await this.deps.bookingRepo.findMany({...});
const user = await this.deps.userRepo.findById({...});
}
}For services/repositories with a single dependency, pass it directly:
// packages/features/myfeature/repositories/MyRepository.ts
export class MyRepository {
constructor(private prismaClient: PrismaClient) {}
async findById(id: string) {
return this.prismaClient.myModel.findUnique({ where: { id } });
}
}**Step 3: Create a module file with moduleLoader**
// packages/features/myfeature/di/MyService.module.ts
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { MyService } from "@calcom/features/myfeature/services/MyService";
import { moduleLoader as bookingRepositoryModuleLoader } from "./BookingRepository.module";
import { moduleLoader as userRepositoryModuleLoader } from "./UserRepository.module";
import { MY_FEATURE_DI_TOKENS } from "./tokens";
const thisModule = createModule();
const token = MY_FEATURE_DI_TOKENS.MY_SERVICE;
const moduleToken = MY_FEATURE_DI_TOKENS.MY_SERVICE_MODULE;
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: MyService,
depsMap: {
bookingRepo: bookingRepositoryModuleLoader,
userRepo: userRepositoryModuleLoader,
},
});
export const moduleLoader: ModuleLoader = {
token,
loadModule,
};
export type { MyService };For a single dependency, use `dep` instead of `depsMap`:
// packages/features/myfeature/di/MyRepository.module.ts
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: MyRepository,
dep: prismaModuleLoader,
});**Step 4: Create a container that uses the moduleLoader**
// packages/features/myfeature/di/MyService.container.
Read more
title: Use Dependency Injection for Loose Coupling impact: HIGH impactDescription: Enables build-time safety, testability, and maintainability tags: patterns, dependency-injection, di, ioctopus, moduleloader, testing, coupling
Use Dependency Injection for Loose Coupling
**Impact: HIGH**
Dependency Injection enables loose coupling, facilitates testing, and isolates concerns. Dependencies should be injected via DI containers rather than instantiated directly within classes.
**Incorrect (tight coupling with direct instantiation):**
class BookingService {
private repository = new BookingRepository();
private emailService = new EmailService();
private calendarService = new GoogleCalendarService();
async createBooking(data: CreateBookingDTO) {
const booking = await this.repository.create(data);
await this.emailService.sendConfirmation(booking);
await this.calendarService.createEvent(booking);
return booking;
}
}**Correct (dependency injection):**
class BookingService {
constructor(
private readonly repository: BookingRepository,
private readonly emailService: EmailService,
private readonly calendarService: CalendarService,
) {}
async createBooking(data: CreateBookingDTO) {
const booking = await this.repository.create(data);
await this.emailService.sendConfirmation(booking);
await this.calendarService.createEvent(booking);
return booking;
}
}**Required patterns:**
- **Application Services**: Orchestrate use cases, coordinate between domain services and repositories
- **Domain Services**: Contain business logic that doesn't naturally belong to a single entity
- **Repositories**: Abstract data access, isolate technology choices
- **Caching Proxies**: Wrap repositories or services to add caching behavior transparently
- **Decorators**: Add cross-cutting concerns (logging, metrics) without polluting domain logic
Cal.diy's Type-Safe DI with moduleLoader
We use `@evyweb/ioctopus` to manage service and repository dependencies. The **moduleLoader pattern** provides type-safe dependency injection, ensuring that if a service adds a new dependency, TypeScript will catch missing dependencies at build time rather than runtime.
Core Concepts
**Tokens**: Unique symbols that identify each service or repository in the DI container. Every injectable class needs a corresponding token. Tokens should be defined in a `tokens.ts` file within the feature's `di/` directory.
**Modules** (`.module.ts` files): Define how classes are instantiated and what dependencies they require using the `bindModuleToClassOnToken` function. Each module exports a `moduleLoader` object that knows how to load itself and its dependencies.
**Containers** (`.container.ts` files): Create a container instance and expose getter functions that consumers use to obtain service instances. Containers use the moduleLoader to automatically load all required dependencies.
**`bindModuleToClassOnToken`**: A type-safe function that binds a class to a token and declares its dependencies. TypeScript ensures the declared dependencies match what the class constructor expects.
How It Works
**Step 1: Create tokens in the feature's di directory**
// packages/features/myfeature/di/tokens.ts
export const MY_FEATURE_DI_TOKENS = {
MY_SERVICE: Symbol("MyService"),
MY_SERVICE_MODULE: Symbol("MyServiceModule"),
};Then import these tokens in the central tokens file:
// packages/features/di/tokens.ts
import { MY_FEATURE_DI_TOKENS } from "@calcom/features/myfeature/di/tokens";
export const DI_TOKENS = {
// ...existing tokens
...MY_FEATURE_DI_TOKENS,
};**Step 2: Define the service class with constructor injection**
For services with multiple dependencies, use a dependencies interface:
// packages/features/myfeature/services/MyService.ts
export interface IMyServiceDeps {
bookingRepo: BookingRepository;
userRepo: UserRepository;
}
export class MyService {
constructor(private deps: IMyServiceDeps) {}
async doSomething() {
const bookings = await this.deps.bookingRepo.findMany({...});
const user = await this.deps.userRepo.findById({...});
}
}For services/repositories with a single dependency, pass it directly:
// packages/features/myfeature/repositories/MyRepository.ts
export class MyRepository {
constructor(private prismaClient: PrismaClient) {}
async findById(id: string) {
return this.prismaClient.myModel.findUnique({ where: { id } });
}
}**Step 3: Create a module file with moduleLoader**
// packages/features/myfeature/di/MyService.module.ts
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { MyService } from "@calcom/features/myfeature/services/MyService";
import { moduleLoader as bookingRepositoryModuleLoader } from "./BookingRepository.module";
import { moduleLoader as userRepositoryModuleLoader } from "./UserRepository.module";
import { MY_FEATURE_DI_TOKENS } from "./tokens";
const thisModule = createModule();
const token = MY_FEATURE_DI_TOKENS.MY_SERVICE;
const moduleToken = MY_FEATURE_DI_TOKENS.MY_SERVICE_MODULE;
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: MyService,
depsMap: {
bookingRepo: bookingRepositoryModuleLoader,
userRepo: userRepositoryModuleLoader,
},
});
export const moduleLoader: ModuleLoader = {
token,
loadModule,
};
export type { MyService };For a single dependency, use `dep` instead of `depsMap`:
// packages/features/myfeature/di/MyRepository.module.ts
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: MyRepository,
dep: prismaModuleLoader,
});**Step 4: Create a container that uses the moduleLoader**
// packages/features/myfeature/di/MyService.container.
Repo: calcom/cal.com
Other agents on caldiy.
- knowledge-base
This file contains domain knowledge about the Cal.diy product and codebase. For coding guidelines and rules, see [`rules/`](rules/).
Open agent - api-no-breaking-changes
**Impact: CRITICAL**
Open agent - api-thin-controllers
**Impact: HIGH**
Open agent - architecture-circular-dependencies
**Impact: CRITICAL**
Open agent - architecture-feature-boundaries
**Impact: CRITICAL**
Open agent - architecture-features-modules
The `packages/features` package should contain only framework-agnostic code: - Repositories (data access layer) - Services (business logic) - Core utilities and helpers - Types and interfaces
Open agent

