cqs-mutations-queries
CQS (Command Query Separation) patterns — mutations for writes with events, queries for reads without side effects.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
CQS (Command Query Separation) patterns — mutations for writes with events, queries for reads without side effects.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Awilix dependency injection patterns for this Fastify project — auto-loading conventions, Cradle usage, partial application, type declarations.
Safe Drizzle ORM migration patterns for PostgreSQL. Auto-loads on migration files. Mandatory reading before writing or editing a migration.
Drizzle ORM query patterns for this project — base repository, soft delete, NON_PASSWORD_COLUMNS, query builder conventions.
Fastify route handler patterns — TypeBox validation, DI access, schema definitions, JWT guards, error handling conventions.
E2E testing patterns using Node.js native test runner — createTestingApp, createDbHelper, factories, app.inject() for HTTP testing.
| name | cqs-mutations-queries |
| description | CQS (Command Query Separation) patterns — mutations for writes with events, queries for reads without side effects. |
| globs | ["src/modules/**/*.mutations.ts","src/modules/**/*.queries.ts","src/modules/**/*.events.ts","src/modules/**/*.event-handlers.ts","src/modules/**/*.domain.ts"] |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash(pnpm:*) |
import type { UUID } from "node:crypto";
import type { Cradle } from "@fastify/awilix";
import { partial } from "rambda";
import { ResourceNotFoundException } from "#libs/errors/domain.errors.ts";
const findOneById = async ({ usersRepository, logger }: Cradle, userId: UUID): Promise<User> => {
logger.debug(`[UsersQueries] Getting user: ${userId}`);
const user = await usersRepository.findOneById(userId);
if (!user) throw new ResourceNotFoundException(`User with id: ${userId} not found`);
return user;
};
export default function usersQueries(deps: Cradle) {
return {
findOneById: partial(findOneById, [deps]),
};
}
Rules for queries:
logger.debug with [ModuleQueries] prefiximport type { Cradle } from "@fastify/awilix";
import { partial } from "rambda";
import { ConflictException, ResourceNotFoundException } from "#libs/errors/domain.errors.ts";
import { USER_EVENTS } from "./users.events.ts";
const createOneUser = async (
{ usersRepository, encrypterService, eventBus, logger }: Cradle,
input: UserCreateInput,
): Promise<User> => {
logger.debug(`[UsersMutations] Creating user: ${input.email}`);
// 1. Validate business rules
const existing = await usersRepository.findOneByEmail(input.email);
if (existing) throw new ConflictException(`User with email: ${input.email} already exists`);
// 2. Transform data
const hashedPassword = await encrypterService.getHash(input.password);
// 3. Persist
const newUser = await usersRepository.createOne({ ...input, password: hashedPassword });
// 4. Emit event AFTER successful write
await eventBus.emit(USER_EVENTS.CREATED, { userId: newUser.id });
// 5. Log success
logger.info(`[UsersMutations] User created: ${newUser.id}`);
return newUser;
};
export default function usersMutations(deps: Cradle) {
return {
createOne: partial(createOneUser, [deps]),
};
}
Rules for mutations:
logger.debug on entry, logger.info on success, with [ModuleMutations] prefix{ userId: string } — handlers fetch fresh data if needed// users.events.ts
export const USER_EVENTS = {
CREATED: "users.created",
UPDATED: "users.updated",
DELETED: "users.deleted",
} as const;
Convention: <module>.<action> in lowercase dot-notation.
// users.event-handlers.ts
import type { Cradle } from "@fastify/awilix";
import { USER_EVENTS } from "./users.events.ts";
export default function setupUsersEventHandlers({ eventBus, logger }: Cradle): void {
eventBus.on(USER_EVENTS.CREATED, async (payload: { userId: string }): Promise<void> => {
logger.info(`[UsersEventHandlers] User created: ${payload.userId}`);
// Trigger side effects: send welcome email, create audit log, etc.
});
}
Rules for event handlers:
[ModuleEventHandlers] prefixPure functions for business rules, extracted from mutations:
// users.domain.ts
export const isEmailTakenByOtherUser = (existingUser: User | undefined, currentUserId: UUID): boolean =>
existingUser !== undefined && existingUser.id !== currentUserId;