Purpose: CacheKit provides reusable caching services and integrations for NestJS applications.
Package: @ciscode/cachekit
Type: Backend NestJS Module Library
Purpose: Reusable cache utilities, adapters, and integrations for backend services
- CSR (Controller-Service-Repository) architecture
- Complete TypeScript configuration with path aliases
- Jest testing setup with 80% coverage threshold
- Changesets for version management
- Husky + lint-staged for code quality
- CI/CD workflows
- Copilot-friendly development guidelines
Modules use Controller-Service-Repository (CSR) pattern for simplicity and reusability.
WHY CSR for modules? Reusable libraries need to be simple, well-documented, and easy to integrate. The 4-layer Clean Architecture is better suited for complex applications, not libraries.
src/
├── index.ts # PUBLIC API exports
├── {module-name}.module.ts # NestJS module definition
│
├── controllers/ # HTTP Layer
│ └── example.controller.ts
│
├── services/ # Business Logic
│ └── example.service.ts
│
├── entities/ # Domain Models
│ └── example.entity.ts
│
├── repositories/ # Data Access
│ └── example.repository.ts
│
├── guards/ # Auth Guards
│ └── example.guard.ts
│
├── decorators/ # Custom Decorators
│ └── example.decorator.ts
│
├── dto/ # Data Transfer Objects
│ └── example.dto.ts
│
├── filters/ # Exception Filters
├── middleware/ # Middleware
├── config/ # Configuration
└── utils/ # Utilities
Responsibility Layers:
| Layer | Responsibility | Examples |
|---|---|---|
| Controllers | HTTP handling, route definition | example.controller.ts |
| Services | Business logic, orchestration | example.service.ts |
| Entities | Domain models (Mongoose/TypeORM schemas) | example.entity.ts |
| Repositories | Data access, database queries | example.repository.ts |
| Guards | Authentication/Authorization | jwt-auth.guard.ts |
| Decorators | Parameter extraction, metadata | @CurrentUser() |
| DTOs | Input validation, API contracts | create-example.dto.ts |
Module Exports (Public API):
// src/index.ts - Only export what apps need to consume
export { ExampleModule } from "./example.module";
// Services (main API)
export { ExampleService } from "./services/example.service";
// DTOs (public contracts)
export { CreateExampleDto, UpdateExampleDto } from "./dto";
// Guards (for protecting routes)
export { ExampleGuard } from "./guards/example.guard";
// Decorators (for DI and metadata)
export { ExampleDecorator } from "./decorators/example.decorator";
// Types & Interfaces (for TypeScript typing)
export type { ExampleOptions, ExampleResult } from "./types";
// ❌ NEVER export entities or repositories
// export { Example } from './entities/example.entity'; // FORBIDDEN
// export { ExampleRepository } from './repositories/example.repository'; // FORBIDDENRationale:
- Entities = internal implementation details (can change)
- Repositories = internal data access (apps shouldn't depend on it)
- DTOs = stable public contracts (apps depend on these)
- Services = public API (apps use methods, not internals)
Pattern: kebab-case + suffix
| Type | Example | Directory |
|---|---|---|
| Controller | example.controller.ts |
controllers/ |
| Service | example.service.ts |
services/ |
| Entity | example.entity.ts |
entities/ |
| Repository | example.repository.ts |
repositories/ |
| DTO | create-example.dto.ts |
dto/ |
| Guard | jwt-auth.guard.ts |
guards/ |
| Decorator | current-user.decorator.ts |
decorators/ |
| Filter | http-exception.filter.ts |
filters/ |
| Middleware | logger.middleware.ts |
middleware/ |
| Utility | validation.utils.ts |
utils/ |
| Config | jwt.config.ts |
config/ |
- Classes & Interfaces:
PascalCase→ExampleController,CreateExampleDto - Variables & Functions:
camelCase→getUserById,exampleList - Constants:
UPPER_SNAKE_CASE→DEFAULT_TIMEOUT,MAX_RETRIES - Enums: Name
PascalCase, valuesUPPER_SNAKE_CASE
enum ExampleStatus {
ACTIVE = "ACTIVE",
INACTIVE = "INACTIVE",
}Configured in tsconfig.json:
"@/*" → "src/*"
"@controllers/*" → "src/controllers/*"
"@services/*" → "src/services/*"
"@entities/*" → "src/entities/*"
"@repos/*" → "src/repositories/*"
"@dtos/*" → "src/dto/*"
"@guards/*" → "src/guards/*"
"@decorators/*" → "src/decorators/*"
"@config/*" → "src/config/*"
"@utils/*" → "src/utils/*"Use aliases for cleaner imports:
import { CreateExampleDto } from "@dtos/create-example.dto";
import { ExampleService } from "@services/example.service";
import { Example } from "@entities/example.entity";Unit Tests - MANDATORY:
- ✅ All services (business logic)
- ✅ All utilities and helpers
- ✅ Guards and decorators
- ✅ Repository methods
Integration Tests:
- ✅ Controllers (full request/response)
- ✅ Module initialization
- ✅ Database operations (with test DB or mocks)
E2E Tests:
- ✅ Complete flows (critical user paths)
Test file location:
src/
└── services/
├── example.service.ts
└── example.service.spec.ts ← Same directory
Jest Configuration:
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
}/**
* Creates a new example record
* @param data - The example data to create
* @returns The created example with generated ID
* @throws {BadRequestException} If data is invalid
* @example
* ```typescript
* const example = await service.create({ name: 'Test' });
* ```
*/
async create(data: CreateExampleDto): Promise<Example>Required for:
- All public functions/methods
- All exported classes
- All DTOs (with property descriptions)
@ApiOperation({ summary: 'Create new example' })
@ApiResponse({ status: 201, description: 'Created successfully', type: ExampleDto })
@ApiResponse({ status: 400, description: 'Invalid input' })
@Post()
async create(@Body() dto: CreateExampleDto) { }Export ONLY public API (Services + DTOs + Guards + Decorators):
// src/index.ts - Public API
export { ExampleModule } from "./example.module";
export { ExampleService } from "./services/example.service";
export { CreateExampleDto, UpdateExampleDto } from "./dto";
export { ExampleGuard } from "./guards/example.guard";
export { ExampleDecorator } from "./decorators/example.decorator";
export type { ExampleOptions } from "./types";❌ NEVER export:
- Entities (internal domain models)
- Repositories (infrastructure details)
Flexible module registration:
@Module({})
export class ExampleModule {
static forRoot(options: ExampleModuleOptions): DynamicModule {
return {
module: ExampleModule,
providers: [{ provide: "EXAMPLE_OPTIONS", useValue: options }, ExampleService],
exports: [ExampleService],
};
}
static forRootAsync(options: ExampleModuleAsyncOptions): DynamicModule {
// Async configuration
}
}- No hardcoded business rules
- Configurable behavior via options
- Database-agnostic (if applicable)
- Apps provide their own connections
1. Branch Creation:
feature/MODULE-123-add-feature
bugfix/MODULE-456-fix-issue
refactor/MODULE-789-improve-code2. Task Documentation: Create task file at branch start:
docs/tasks/active/MODULE-123-add-feature.md
3. On Release: Move to archive:
docs/tasks/archive/by-release/v2.0.0/MODULE-123-add-feature.md
Simple changes:
- Read context → Implement → Update docs → Create changeset
Complex changes:
- Read context → Discuss approach → Implement → Update docs → Create changeset
When blocked:
- DO: Ask immediately
- DON'T: Generate incorrect output
MAJOR (x.0.0) - Breaking changes:
- Changed function signatures
- Removed public methods
- Changed DTOs structure
- Changed module configuration
MINOR (0.x.0) - New features:
- New endpoints/methods
- New optional parameters
- New decorators/guards
PATCH (0.0.x) - Bug fixes:
- Internal fixes
- Performance improvements
- Documentation updates
ALWAYS create a changeset for user-facing changes:
npx changesetWhen to create a changeset:
- ✅ New features
- ✅ Bug fixes
- ✅ Breaking changes
- ✅ Performance improvements
- ❌ Internal refactoring (no user impact)
- ❌ Documentation updates only
- ❌ Test improvements only
Before completing any task:
- Code implemented
- Tests passing
- Documentation updated
- Changeset created ← CRITICAL
- PR ready
Changeset format:
---
"@ciscode/example-kit": minor
---
Added support for custom validators in ExampleServiceChangesets automatically generates CHANGELOG. For manual additions:
# Changelog
## [2.0.0] - 2026-02-03
### BREAKING CHANGES
- `create()` now requires `userId` parameter
- Removed deprecated `validateExample()` method
### Added
- New `ExampleGuard` for route protection
- Support for async configuration
### Fixed
- Fixed validation edge caseALWAYS:
- ✅ Input validation on all DTOs (class-validator)
- ✅ JWT secret from env (never hardcoded)
- ✅ Rate limiting on public endpoints
- ✅ No secrets in code
- ✅ Sanitize error messages (no stack traces in production)
Example:
export class CreateExampleDto {
@IsString()
@MinLength(3)
@MaxLength(50)
name: string;
@IsEmail()
email: string;
}NEVER without approval:
- Breaking changes to public API
- Changing exported DTOs/interfaces
- Removing exported functions
- Major dependency upgrades
- Security-related changes
CAN do autonomously:
- Bug fixes (no breaking changes)
- Internal refactoring
- Adding new features (non-breaking)
- Test improvements
- Documentation updates
Before publishing:
- All tests passing (100% of test suite)
- Coverage >= 80%
- No ESLint warnings (
--max-warnings=0) - TypeScript strict mode passing
- All public APIs documented (JSDoc)
- README updated with examples
- Changeset created
- Breaking changes highlighted
- Integration tested with sample app
- Clone module repo
- Create branch:
feature/TASK-123-descriptionfromdevelop - Implement with tests
- Create changeset:
npx changeset - Verify checklist
- Create PR →
develop
# In module
npm link
# In app
cd ~/comptaleyes/backend
npm link @ciscode/example-kit
# Develop and test
# Unlink when done
npm unlink @ciscode/example-kit- ESLint
--max-warnings=0 - Prettier formatting
- TypeScript strict mode
- FP for logic, OOP for structure
- Dependency injection via constructor
Example:
@Injectable()
export class ExampleService {
constructor(
private readonly repo: ExampleRepository,
private readonly logger: LoggerService,
) {}
}Custom domain errors:
export class ExampleNotFoundError extends Error {
constructor(id: string) {
super(`Example ${id} not found`);
this.name = "ExampleNotFoundError";
}
}Structured logging:
this.logger.error("Operation failed", {
exampleId: id,
reason: "validation_error",
timestamp: new Date().toISOString(),
});NEVER silent failures:
// ❌ WRONG
try {
await operation();
} catch (error) {
// Silent failure
}
// ✅ CORRECT
try {
await operation();
} catch (error) {
this.logger.error("Operation failed", { error });
throw error;
}- Brief and direct
- Focus on results
- Module-specific context
- Highlight breaking changes immediately
Module Principles:
- Reusability over specificity
- Comprehensive testing (80%+)
- Complete documentation
- Strict versioning
- Breaking changes = MAJOR bump + changeset
- Zero app coupling
- Configurable behavior
When in doubt: Ask, don't assume. Modules impact multiple projects.
Last Updated: February 3, 2026
Version: 2.0.0