# Create Handler

> **Prerequisite**: this reference applies only when the handler file already exists on disk — the
> CLI generated it from a standard `create` operation declared in the module's YAML. If the file
> does not exist, return to Step 0 of `SKILL.md`: edit the YAML, run
> `catalyst load back module --name=<bc>/<module> --force`, and the CLI emits the handler, service,
> resolver, controller, DTO, and GraphQL fragment together. Hand-writing the handler bypasses the
> generator and breaks on the next regeneration.

## When it applies

File matches `*-create-*.handler.ts` under `@app/<pkg>/<module>/application/create/`. A create
handler is the write path that accepts a payload, validates it, emits a creation event, and returns
the newly-created entity.

## Canonical signature

Exact shape from `backend/src/@app/iam/tag/application/create/iam-create-tag.handler.ts`:

```typescript
/**
 * @aurora-catalyst-generated
 * @source cliter/iam/tag.aurora.yaml
 */
import { IamCreateTagInput, IamTag } from '@api/graphql';
import { IamCreateTagService, IamFindTagByIdService } from '@app/iam/tag';
import { IamTagFieldSchema } from '@app/iam/tag/domain';
import { ApplySchema, EmitEvent, HandlerMeta } from '@aurorajs.dev/core-back';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

@Injectable()
export class IamCreateTagHandler {
  constructor(
    private readonly eventEmitter: EventEmitter2,
    private readonly createService: IamCreateTagService,
    private readonly findByIdService: IamFindTagByIdService,
  ) {}

  @EmitEvent('iam.tag.created')
  @ApplySchema(IamTagFieldSchema)
  async main(payload: IamCreateTagInput, handlerMeta?: HandlerMeta): Promise<IamTag> {
    await this.createService.main(payload, handlerMeta);

    return await this.findByIdService.main(payload.id, {}, handlerMeta);
  }
}
```

Role of each moving part:

- **`@Injectable()`** — standard NestJS DI. No `@CommandHandler`.
- **Constructor deps** — the event emitter is injected because the `@EmitEvent` decorator reads it
  off `this`; the create service persists; the find-by-id service returns the freshly-created entity
  in its fully-hydrated shape so the resolver does not have to re-query.
- **`@EmitEvent('iam.tag.created')`** — publishes on `EventEmitter2` after `main()` resolves without
  throwing. You do not call `eventEmitter.emit(...)` by hand.
- **`@ApplySchema(IamTagFieldSchema)`** — runs declarative validation over `payload` before `main()`
  begins. Anything the `FieldSchema` expresses (required fields, maxLength, enum membership, custom
  rules) is enforced here; do not duplicate those checks inside `main()`.
- **`payload`** — the GraphQL input type (or DTO in REST controllers); the type comes from
  `@api/graphql`.
- **`handlerMeta?`** — carries `timezone` and `repositoryOptions.auditing`. The resolver or
  controller builds it; the handler forwards it to every service call. Do not construct it inside
  the handler.
- **Return shape** — the entity type from `@api/graphql`. Catalyst handlers return the full entity
  (not just an id) so the resolver can short-circuit the follow-up query.

## Typical `main()` body

```
1. (optional) pre-persistence validation — repository lookups, business rules, conflict checks.
2. call createService.main(payload, handlerMeta) to persist.
3. call findByIdService.main(payload.id, {}, handlerMeta) to return the hydrated entity.
```

The service is thin: it does not validate, it does not emit. It creates the aggregate from the
payload and writes it.

## Expected errors

- **`ConflictException`** — when a pre-persistence uniqueness check finds an existing entity that
  would collide with the payload.
- **`ValidationException`** / field-level throws from `@ApplySchema` — surface automatically before
  `main()` runs, so you never need to re-throw them.
- **`BadRequestException`** — when business rules reject the payload for semantic reasons that the
  `FieldSchema` cannot express (e.g., "role X cannot own resource Y because of a relationship
  constraint").

## Pre-persistence validation placement

Put validation **before** `this.createService.main(...)`. Anything after the service call runs on an
already-persisted aggregate, which is too late to reject cleanly.

```typescript
@EmitEvent('iam.tag.created')
@ApplySchema(IamTagFieldSchema)
async main(payload: IamCreateTagInput, handlerMeta?: HandlerMeta): Promise<IamTag> {
  // Pre-persistence: reject if a tag with the same slug already exists in the same tenant.
  const existing = await this.repository.find({
    where: { slug: payload.slug, tenantId: payload.tenantId },
  });

  if (existing) {
    throw new ConflictException(`IamTag with slug '${payload.slug}' already exists`);
  }

  await this.createService.main(payload, handlerMeta);
  return await this.findByIdService.main(payload.id, {}, handlerMeta);
}
```

If the check requires a repository that the generated handler does not inject, add it to the
constructor — it is a hand edit the CLI preserves when you mark the block with the canonical
`@aurora-catalyst-generated` boundary rules (see `catalyst-project-structure`).

## Archetype-specific anti-patterns

- **Validation in the service.** The service is a thin persistence wrapper. Put the pre-persistence
  query or rule in the handler so the service stays regenerable.
- **Manual `eventEmitter.emit(...)` call.** Let `@EmitEvent` do it. A manual call duplicates the
  emission or, worse, emits before persistence succeeded.
- **Returning the payload instead of the persisted entity.** The payload may diverge from the
  persisted row (server-side defaults, generated timestamps, resolved relations). Always return via
  `findByIdService.main(payload.id, {}, handlerMeta)`.
