Domain-Driven Design patterns and architecture for Deno TypeScript applications. Use when building complex business logic, implementing bounded contexts, or structuring large-scale Deno applications with clear separation of concerns.
Provides Domain-Driven Design patterns and hexagonal architecture for Deno TypeScript applications. Use when building complex business logic, implementing bounded contexts, or structuring large-scale applications with clear separation of concerns.
/plugin marketplace add jahanson/cc-plugins/plugin install deno-lsp@local-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Use this skill when:
Prerequisites: Always read deno-core.md first for essential Deno configuration.
Clean, Modern TypeScript: Embrace Deno's vision of secure, modern JavaScript/TypeScript development without the baggage of Node.js legacy patterns.
Domain-Driven Design: Follow DDD principles with clear separation between domain logic, application services, and infrastructure concerns.
TypeScript-First: Leverage TypeScript's type system for safety and developer experience. No
anytypes in production code.
src/
├── domain/ # Domain layer - core business logic
│ ├── entities/ # Domain entities (Memory, User, Order)
│ │ ├── user.ts
│ │ └── order.ts
│ ├── value-objects/ # Immutable values (Email, Money, Status)
│ │ ├── email.ts
│ │ ├── money.ts
│ │ └── order-status.ts
│ ├── aggregates/ # Consistency boundaries
│ │ └── order-aggregate.ts
│ ├── repositories/ # Repository interfaces (ports)
│ │ ├── user-repository.ts
│ │ └── order-repository.ts
│ ├── services/ # Domain services
│ │ └── pricing-service.ts
│ ├── events/ # Domain events
│ │ └── order-created.ts
│ └── errors/ # Domain-specific errors
│ ├── validation-error.ts
│ └── business-rule-error.ts
│
├── application/ # Application layer - use cases
│ ├── use-cases/ # Use case implementations
│ │ ├── create-order.ts
│ │ ├── update-user.ts
│ │ └── process-payment.ts
│ ├── services/ # Application services
│ ├── dto/ # Data transfer objects
│ │ ├── create-order-dto.ts
│ │ └── user-response-dto.ts
│ └── errors/ # Application-specific errors
│ ├── not-found-error.ts
│ └── unauthorized-error.ts
│
├── infrastructure/ # Infrastructure layer - technical details
│ ├── persistence/ # Database implementations
│ │ ├── postgres/
│ │ │ ├── user-repository-impl.ts
│ │ │ └── order-repository-impl.ts
│ │ └── migrations/
│ ├── external/ # External service integrations
│ │ ├── payment-gateway.ts
│ │ └── email-service.ts
│ ├── logging/ # Structured logging
│ │ └── logger.ts
│ ├── config/ # Configuration
│ │ └── database.ts
│ └── errors/ # Infrastructure errors
│ ├── database-error.ts
│ └── external-api-error.ts
│
├── web/ # Web/API layer - HTTP entry points
│ ├── controllers/ # Request handlers
│ │ ├── user-controller.ts
│ │ └── order-controller.ts
│ ├── middleware/ # HTTP middleware
│ │ ├── auth.ts
│ │ ├── validation.ts
│ │ ├── error-handler.ts
│ │ └── logging.ts
│ ├── routes/ # Route definitions
│ │ ├── user-routes.ts
│ │ └── order-routes.ts
│ └── server.ts # HTTP server setup
│
└── shared/ # Shared kernel
├── types/
│ └── result.ts
└── utils/
└── validation.ts
tests/
├── domain/ # Domain tests (unit)
│ ├── entities/
│ │ └── user.test.ts
│ └── value-objects/
│ └── email.test.ts
├── application/ # Application tests (integration)
│ └── use-cases/
│ └── create-order.test.ts
└── e2e/ # End-to-end tests
└── order-workflow.test.ts
Configure deno.json for clean imports across all layers:
{
"imports": {
"@/": "./src/",
"@/domain/": "./src/domain/",
"@/application/": "./src/application/",
"@/infrastructure/": "./src/infrastructure/",
"@/web/": "./src/web/",
"@/shared/": "./src/shared/"
}
}
Understanding and enforcing layer dependencies is critical for maintaining a clean DDD architecture.
domain → (no external dependencies - pure business logic)application → domaininfrastructure → domain + applicationweb (or api) → domain + application + infrastructureNEVER allow these dependencies:
domain → application, infrastructure, webapplication → infrastructure, webinfrastructure → web ┌─────────────┐
│ web │ (HTTP handlers, routes, middleware)
└──────┬──────┘
│
┌──────▼──────┐
│infrastructure│ (Database, external APIs)
└──────┬──────┘
│
┌──────▼──────┐
│ application │ (Use cases, orchestration)
└──────┬──────┘
│
┌──────▼──────┐
│ domain │ (Entities, value objects, business rules)
└─────────────┘
Key Principle: Dependencies flow inward. Inner layers have no knowledge of outer layers.
Entities have identity and lifecycle. Use classes with private constructors.
// src/domain/entities/user.ts
import type { Email } from "@/domain/value-objects/email.ts";
import type { UserId } from "@/domain/value-objects/user-id.ts";
export class User {
private constructor(
private readonly id: UserId,
private name: string,
private email: Email,
private readonly createdAt: Date,
) {}
// Factory method - ensures valid construction
static create(name: string, email: Email): User {
if (name.trim().length === 0) {
throw new Error("User name cannot be empty");
}
return new User(
UserId.generate(),
name,
email,
new Date(),
);
}
// Reconstruct from persistence
static reconstitute(
id: UserId,
name: string,
email: Email,
createdAt: Date,
): User {
return new User(id, name, email, createdAt);
}
// Business logic methods
changeName(newName: string): void {
if (newName.trim().length === 0) {
throw new Error("User name cannot be empty");
}
this.name = newName;
}
// Getters
getId(): UserId { return this.id; }
getName(): string { return this.name; }
getEmail(): Email { return this.email; }
getCreatedAt(): Date { return this.createdAt; }
}
Value objects have no identity, compared by value.
// src/domain/value-objects/email.ts
export class Email {
private constructor(private readonly value: string) {}
static create(value: string): Email {
if (!Email.isValid(value)) {
throw new Error(`Invalid email: ${value}`);
}
return new Email(value.toLowerCase());
}
private static isValid(value: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
}
getValue(): string { return this.value; }
equals(other: Email): boolean { return this.value === other.value; }
toString(): string { return this.value; }
}
// src/domain/value-objects/money.ts
export class Money {
private constructor(
private readonly amount: number,
private readonly currency: string,
) {}
static create(amount: number, currency: string): Money {
if (amount < 0) {
throw new Error("Money amount cannot be negative");
}
if (!["USD", "EUR", "GBP"].includes(currency)) {
throw new Error(`Unsupported currency: ${currency}`);
}
return new Money(amount, currency);
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error("Cannot add money with different currencies");
}
return new Money(this.amount + other.amount, this.currency);
}
multiply(factor: number): Money {
return new Money(this.amount * factor, this.currency);
}
getAmount(): number { return this.amount; }
getCurrency(): string { return this.currency; }
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}
Aggregates enforce consistency boundaries and business invariants.
// src/domain/aggregates/order-aggregate.ts
import type { OrderId } from "@/domain/value-objects/order-id.ts";
import type { Money } from "@/domain/value-objects/money.ts";
import type { OrderLine } from "@/domain/entities/order-line.ts";
export enum OrderStatus {
PENDING = "PENDING",
CONFIRMED = "CONFIRMED",
SHIPPED = "SHIPPED",
DELIVERED = "DELIVERED",
CANCELLED = "CANCELLED",
}
export class Order {
private constructor(
private readonly id: OrderId,
private status: OrderStatus,
private readonly lines: OrderLine[],
private readonly createdAt: Date,
) {}
static create(lines: OrderLine[]): Order {
if (lines.length === 0) {
throw new Error("Order must have at least one line");
}
return new Order(
OrderId.generate(),
OrderStatus.PENDING,
lines,
new Date(),
);
}
// Business logic - enforce invariants
confirm(): void {
if (this.status !== OrderStatus.PENDING) {
throw new Error(`Cannot confirm order with status ${this.status}`);
}
this.status = OrderStatus.CONFIRMED;
}
cancel(): void {
if (this.status === OrderStatus.SHIPPED || this.status === OrderStatus.DELIVERED) {
throw new Error("Cannot cancel shipped or delivered order");
}
this.status = OrderStatus.CANCELLED;
}
calculateTotal(): Money {
return this.lines.reduce(
(total, line) => total.add(line.getSubtotal()),
Money.create(0, "USD"),
);
}
getId(): OrderId { return this.id; }
getStatus(): OrderStatus { return this.status; }
getLines(): ReadonlyArray<OrderLine> { return this.lines; }
}
Define interfaces in domain layer, implement in infrastructure.
// src/domain/repositories/user-repository.ts
import type { User } from "@/domain/entities/user.ts";
import type { UserId } from "@/domain/value-objects/user-id.ts";
import type { Email } from "@/domain/value-objects/email.ts";
export interface UserRepository {
save(user: User): Promise<void>;
findById(id: UserId): Promise<User | null>;
findByEmail(email: Email): Promise<User | null>;
delete(id: UserId): Promise<void>;
}
Use cases orchestrate domain logic without containing business rules.
// src/application/use-cases/create-order.ts
import type { UserRepository } from "@/domain/repositories/user-repository.ts";
import type { OrderRepository } from "@/domain/repositories/order-repository.ts";
import { Order } from "@/domain/aggregates/order-aggregate.ts";
import { OrderLine } from "@/domain/entities/order-line.ts";
import type { CreateOrderDto } from "@/application/dto/create-order-dto.ts";
import { Result } from "@/shared/types/result.ts";
export class CreateOrderUseCase {
constructor(
private readonly userRepository: UserRepository,
private readonly orderRepository: OrderRepository,
) {}
async execute(dto: CreateOrderDto): Promise<Result<Order>> {
try {
const user = await this.userRepository.findById(dto.userId);
if (!user) {
return Result.fail("User not found");
}
const lines = dto.items.map((item) =>
OrderLine.create(item.productId, item.quantity, item.price)
);
const order = Order.create(lines);
await this.orderRepository.save(order);
return Result.ok(order);
} catch (error) {
return Result.fail(error.message);
}
}
}
// src/domain/errors/validation-error.ts
export class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
// src/application/errors/not-found-error.ts
export class MemoryNotFoundError extends Error {
constructor(id: string) {
super(`Memory with id ${id} not found`);
this.name = "MemoryNotFoundError";
}
}
// src/infrastructure/errors/database-error.ts
export class DatabaseError extends Error {
constructor(message: string, public readonly cause?: Error) {
super(message);
this.name = "DatabaseError";
}
}
// src/web/middleware/error-handler.ts
import { ValidationError } from "@/domain/errors/validation-error.ts";
import { MemoryNotFoundError } from "@/application/errors/memory-not-found-error.ts";
import { DatabaseError } from "@/infrastructure/errors/database-error.ts";
export function errorHandler(error: Error): Response {
if (error instanceof ValidationError) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
if (error instanceof MemoryNotFoundError) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
if (error instanceof DatabaseError) {
console.error("Database error:", error);
return new Response(
JSON.stringify({ error: "Database service unavailable" }),
{ status: 503, headers: { "Content-Type": "application/json" } },
);
}
console.error("Unexpected error:", error);
return new Response(
JSON.stringify({ error: "Internal server error" }),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
Avoid throwing exceptions across boundaries.
// src/shared/types/result.ts
export class Result<T> {
private constructor(
private readonly success: boolean,
private readonly value?: T,
private readonly error?: string,
) {}
static ok<T>(value: T): Result<T> {
return new Result(true, value);
}
static fail<T>(error: string): Result<T> {
return new Result(false, undefined, error);
}
isSuccess(): boolean { return this.success; }
isFailure(): boolean { return !this.success; }
getValue(): T {
if (!this.success) throw new Error("Cannot get value from failed result");
return this.value!;
}
getError(): string {
if (this.success) throw new Error("Cannot get error from successful result");
return this.error!;
}
}
// BAD - No behavior, just data
export class User {
id: string;
name: string;
email: string;
}
// GOOD - Rich domain model
export class User {
private name: string;
changeName(newName: string): void {
if (newName.trim().length === 0) {
throw new Error("Name cannot be empty");
}
this.name = newName;
}
}
// BAD - Domain depends on infrastructure
import { Pool } from "@db/postgres";
export class User {
async save(pool: Pool): Promise<void> { /* ... */ }
}
// GOOD - Domain defines interface
export interface UserRepository {
save(user: User): Promise<void>;
}
// BAD - Direct access to mutable array
export class Order {
public lines: OrderLine[] = [];
}
// GOOD - Encapsulation with readonly
export class Order {
private readonly lines: OrderLine[];
getLines(): ReadonlyArray<OrderLine> {
return this.lines;
}
}
any types in production codeThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.