Skill
monotemplate:di (new skill)
Problem
There is no skill documenting the dependency injection pattern used in the monotemplate. When Claude adds new entities (repositories, controllers, services), it needs to understand how to wire them into the Inversify DI container correctly. Without this guidance, Claude may create classes without @injectable() decorators, forget @inject() on constructor parameters, skip registering bindings in the container, or misunderstand the simulated auth/__dev__ gating patterns.
Proposed Solution
Create a monotemplate:di skill that documents the Inversify DI pattern as implemented in the template. The skill should cover:
Core Pattern
The template uses Inversify v7 with reflect-metadata for decorator-based dependency injection. All DI configuration lives in apps/server/src/di/container.ts, which exports a singleton container instance.
Key Concepts to Document
1. Making a class injectable
- Every Repository, Controller, and Service class must be decorated with
@injectable()
- Constructor dependencies use
@inject(ClassName) parameter decorators
- Import the class itself (not
type-only) so the class token is available at runtime
import { injectable, inject } from "inversify";
import { SomeRepository } from "@server/repositories/SomeRepository";
@injectable()
export class SomeController {
constructor(@inject(SomeRepository) private someRepository: SomeRepository) {}
}
2. Registering bindings in container.ts
- Classes that auto-resolve their own deps:
container.bind(ClassName).toSelf().inSingletonScope()
- External/constant values (PrismaClient, env-gated services):
container.bind(Token).toConstantValue(value)
- Abstract → concrete (auth):
container.bind(AbstractClass).to(ConcreteClass).inSingletonScope()
- Multi-bindings (middleware):
container.bind(SYMBOL_TOKEN).toConstantValue(value) (called multiple times)
3. Resolving dependencies
- In routers/handlers:
container.get(ClassName) — import both container and the class
- In
server.ts: container.get(AuthContextFactory), container.getAll<T>(EXPRESS_MIDDLEWARE)
- Never destructure from a container object; always use
container.get()
4. Binding categories and order in container.ts
// Database
container.bind(PrismaClient).toConstantValue(prisma);
// Middleware (multi-binding via Symbol token)
container.bind(EXPRESS_MIDDLEWARE).toConstantValue(helmet());
container.bind(EXPRESS_MIDDLEWARE).toConstantValue(express.json());
// Auth (abstract → concrete, with simulated mode gating)
if (isSimulated()) {
container.bind(AuthContextFactory).to(SimulatedAuthContextFactory).inSingletonScope();
} else {
container.bind(AuthContextFactory).to(ClerkAuthContextFactory).inSingletonScope();
}
// Repositories
container.bind(FooRepository).toSelf().inSingletonScope();
// Controllers
container.bind(FooController).toSelf().inSingletonScope();
5. Symbol tokens (di/tokens.ts)
- Used for multi-bindings where multiple values share one key (e.g.,
EXPRESS_MIDDLEWARE)
- Defined as
export const TOKEN_NAME = Symbol.for("TokenName");
6. Simulated / E2E auth gating
__dev__/ directory code is dynamically imported inside isSimulated() checks (Bun macro)
- Never use static imports for
__dev__/ code in production paths
- Pattern:
const { SimClass } = await import("@server/__dev__/SimClass");
7. tsconfig requirements
experimentalDecorators: true and emitDecoratorMetadata: true must be set
import "reflect-metadata" must be the first import in the entry point (index.ts)
8. When adding a new entity (checklist)
- Add
@injectable() to the class
- Add
@inject(Dep) to each constructor parameter
- Import classes (not
type-only) for injected dependencies
- Register binding in
container.ts under the appropriate section
- In routers, resolve via
container.get(ClassName)
Trigger Description
Use when adding new repositories, controllers, services, or middleware to the server. Use when wiring dependencies, configuring the DI container, or understanding how classes are resolved.
Examples
Before (manual wiring — old pattern):
export const createContainer = () => {
const prisma = new PrismaClient({ adapter });
const usersRepository = new UsersRepository(prisma);
const usersController = new UsersController(usersRepository);
return { prisma, usersRepository, usersController };
};
After (Inversify DI — current pattern):
// container.ts
const container = new Container();
container.bind(PrismaClient).toConstantValue(prisma);
container.bind(UsersRepository).toSelf().inSingletonScope();
container.bind(UsersController).toSelf().inSingletonScope();
export { container };
// router.ts
import { container } from "@server/di/container";
import { UsersController } from "@server/controllers/UsersController";
const result = await container.get(UsersController).getUserById(id);
References
Skill
monotemplate:di(new skill)Problem
There is no skill documenting the dependency injection pattern used in the monotemplate. When Claude adds new entities (repositories, controllers, services), it needs to understand how to wire them into the Inversify DI container correctly. Without this guidance, Claude may create classes without
@injectable()decorators, forget@inject()on constructor parameters, skip registering bindings in the container, or misunderstand the simulated auth/__dev__gating patterns.Proposed Solution
Create a
monotemplate:diskill that documents the Inversify DI pattern as implemented in the template. The skill should cover:Core Pattern
The template uses Inversify v7 with
reflect-metadatafor decorator-based dependency injection. All DI configuration lives inapps/server/src/di/container.ts, which exports a singletoncontainerinstance.Key Concepts to Document
1. Making a class injectable
@injectable()@inject(ClassName)parameter decoratorstype-only) so the class token is available at runtime2. Registering bindings in
container.tscontainer.bind(ClassName).toSelf().inSingletonScope()container.bind(Token).toConstantValue(value)container.bind(AbstractClass).to(ConcreteClass).inSingletonScope()container.bind(SYMBOL_TOKEN).toConstantValue(value)(called multiple times)3. Resolving dependencies
container.get(ClassName)— import bothcontainerand the classserver.ts:container.get(AuthContextFactory),container.getAll<T>(EXPRESS_MIDDLEWARE)container.get()4. Binding categories and order in
container.ts5. Symbol tokens (
di/tokens.ts)EXPRESS_MIDDLEWARE)export const TOKEN_NAME = Symbol.for("TokenName");6. Simulated / E2E auth gating
__dev__/directory code is dynamically imported insideisSimulated()checks (Bun macro)__dev__/code in production pathsconst { SimClass } = await import("@server/__dev__/SimClass");7. tsconfig requirements
experimentalDecorators: trueandemitDecoratorMetadata: truemust be setimport "reflect-metadata"must be the first import in the entry point (index.ts)8. When adding a new entity (checklist)
@injectable()to the class@inject(Dep)to each constructor parametertype-only) for injected dependenciescontainer.tsunder the appropriate sectioncontainer.get(ClassName)Trigger Description
Use when adding new repositories, controllers, services, or middleware to the server. Use when wiring dependencies, configuring the DI container, or understanding how classes are resolved.
Examples
Before (manual wiring — old pattern):
After (Inversify DI — current pattern):
References