---
name: catalyst-field-schema
description: >
  Teach the declarative data-access layer of the Aurora Catalyst backend: the `FieldSchema`
  interface and 16 type handlers, the three decorators `@ApplySchema` / `@Format` / `@Sanitize`
  (with decision tree and `payloadIndex` option), the `Obj.diff` helper, and the query side —
  `QueryStatement` shape, the 40+ value `Operator` enum, `include` for eager-loading relations. The
  skill is CLI-first: `*.field-schema.ts` files are generated from the module's YAML
  `aggregateProperties`. Trigger: wiring `@ApplySchema` / `@Format` / `@Sanitize` on any
  `*.handler.ts`; composing a `QueryStatement` (`where` / `include` / `order` / `limit` / `offset`);
  reaching for an operator (`iLike`, `in`, `between`, `eq`, `and`, `or`, …); reasoning about a
  `*.field-schema.ts` file; understanding what `type: 'password'` / `type: 'timestamp'` / `type:
  'enum'` does at runtime; computing a delta with `Obj.diff` inside an update handler. Fires even if
  the user says only "add a field to tag" or "filter accounts by email pattern" — Step 0 of this
  skill routes the YAML + regenerate case and the query side respectively. Does NOT apply to YAML
  authoring itself (use `catalyst-schema` for that) and does NOT replace full-module audits (use
  `catalyst-review-module`).
license: MIT
metadata:
  author: aurora
  version: '1.0'
---

## Step 0 — CLI-First Gate (Read Before Anything Else)

Before touching any file, answer: **"should I be writing this file by hand at all?"** The
`*.field-schema.ts` files are CLI-generated from the module's YAML `aggregateProperties`, same as
handlers, services, controllers, resolvers, DTOs. Hand-editing a field-schema bypasses the
generator; the next `catalyst load` detects the integrity-hash mismatch and emits a `.origin`
conflict.

Route every field-schema task through one of these three branches.

```
┌──────────────────────────────────────────────────────────────────────┐
│  What do you actually need?                                          │
└────────────────────────────┬─────────────────────────────────────────┘
                             │
   ┌─────────────────────────┼─────────────────────────┐
   │                         │                         │
   ▼                         ▼                         ▼
┌──────────────┐   ┌───────────────────────┐   ┌──────────────────┐
│ Add / change │   │ Change a type's       │   │ Hand-edit a      │
│ a field      │   │ fundamental behavior  │   │ *.field-schema.ts│
│              │   │ (e.g. password's      │   │ file directly    │
│              │   │ bcrypt rounds, a new  │   │                  │
│              │   │ type handler)         │   │                  │
└──────┬───────┘   └───────────┬───────────┘   └─────────┬────────┘
       │                       │                         │
       ▼                       ▼                         ▼
Edit YAML              Upstream change to          STOP.
aggregateProperties.   @aurorajs.dev/core-back.    Surface to user.
Run catalyst load.     Out of scope for this       The next regen will
CLI (re)generates      skill. File an issue        overwrite or emit a
*.field-schema.ts.     or SPEC in the CLI          .origin conflict.
                       ROADMAP.

Read                                              If you truly need a
`references/field-                                hand edit (rare),
schema-and-decorators                             document WHY in a
.md` for the semantics                            code comment and
of the generated                                  accept the .origin
schema.                                           review cycle on
                                                  every regeneration.
```

**The regeneration command** (same as handler-composer's Step 0):

```bash
catalyst load back module --name=<bounded-context>/<module> --force
```

Full command documentation, YAML switches, and lockfile interaction:
[`catalyst-handler-composer/references/cli-first-workflow.md`](../catalyst-handler-composer/references/cli-first-workflow.md).

Only once Step 0 has routed the task — and the target file exists on disk — proceed to the
pre-flight checklist below.

## When to Use

- Wiring `@ApplySchema`, `@Format`, or `@Sanitize` on a handler's `main()` method.
- Deciding which of the three decorators to reach for given the handler's input/output shape.
- Reading a `*.field-schema.ts` file to understand what a field declares about its type,
  nullability, length, enum values, or `rules`.
- Composing a `QueryStatement` — `where` predicate, `include` relation, `order`, `limit`, `offset`.
- Reaching for an operator (`Operator.iLike`, `Operator.in`, `Operator.between`, …) to express a
  filter predicate in a custom handler.
- Computing a delta between an incoming payload and the persisted record via `Obj.diff`.

This skill is **COMPOSE / REFERENCE** — it teaches how to use the declarative layer. For full-module
audit see `catalyst-review-module`; for YAML authoring itself see `catalyst-schema` (monorepo root
scope).

## Pre-flight Checklist (Blocking)

**Prerequisite**: Step 0 has already routed the task. With the file in hand, match the task shape
against this table and read the referenced file in full before editing code.

| Task shape                                                                | Reference to read                                                                                              |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Wiring `@ApplySchema` / `@Format` / `@Sanitize` on a handler              | [`references/field-schema-and-decorators.md`](references/field-schema-and-decorators.md)                       |
| Reasoning about a `*.field-schema.ts` entry (type, nullable, rules, …)    | [`references/field-schema-and-decorators.md`](references/field-schema-and-decorators.md)                       |
| `Obj.diff` usage inside an update handler                                 | [`references/field-schema-and-decorators.md`](references/field-schema-and-decorators.md)                       |
| Composing a `QueryStatement` (`where` / `include` / `order` / pagination) | [`references/query-statement.md`](references/query-statement.md)                                               |
| Picking an `Operator` value (`iLike`, `in`, `between`, `and`, …)          | [`references/query-statement.md`](references/query-statement.md)                                               |
| Loading a relation via `include` (recursive formatting via `relation` entries) | [`references/query-statement.md`](references/query-statement.md) — read "Relation formatting"             |
| Hand-edit a `*.field-schema.ts` file                                      | **BLOCK** — return to Step 0 above                                                                             |

## Decorator Decision Tree (Compact)

Full discussion in
[`references/field-schema-and-decorators.md`](references/field-schema-and-decorators.md); this is
the 20-second version.

```
Does the handler RECEIVE a payload?
  │
  ├── No  → @Format(schema)   (read handlers: findById, find, get, paginate)
  │
  └── Yes
        │
        └── Does the handler RETURN the entity?
              │
              ├── Yes → @ApplySchema(schema)   (create, update-by-id, delete with return)
              │
              └── No  → @Sanitize(schema)       (write-only flows, no entity in response)
```

Non-zero-index payload? Use the object form: `@ApplySchema({ schema, payloadIndex: N })` or
`@Sanitize({ schema, payloadIndex: N })` (`@Format` has no payloadIndex — it operates on the return
value).

## Anti-patterns

1. **Hand-editing `*.field-schema.ts`.** → Edit the YAML's `aggregateProperties` and run
   `catalyst load back module --name=<bc>/<module> --force`. The generator owns the file.

2. **String-literal operators** (`'[iLike]'`, `'[eq]'`, `'[and]'`, …). → Import `Operator` from
   `@aurorajs.dev/core-common` and use `Operator.iLike`, `Operator.eq`, `Operator.and`. TypeScript
   catches typos on the enum; it cannot on a string literal.

3. **Adding a manual `formatRecord()` call on an included relation.** → Not needed since
   `@aurorajs.dev/core-back@1.1.0` (capability `schema-pipeline`). The pipeline recurses through
   `relation` entries emitted by the CLI-generated schemas, applying each handler to nested
   sub-records automatically. A legacy manual call is a red flag: it masks the fact that the
   generated `FieldSchema` may not declare the expected `relation` entry. Delete the manual call
   and verify the schema includes `{ name: '<alias>', type: 'relation', cardinality, target }`.
   Requirements: consumer on `core-back@>=1.1.0`, schemas regenerated by a CLI that emits
   `relation` entries. See [`references/query-statement.md`](references/query-statement.md)
   "Relation formatting" section.

4. **Duplicating `FieldSchema` validation inside the handler's `main()`.** → `@ApplySchema` already
   enforces `maxLength`, `nullable`, `enumOptions`, and declared `rules`. A redundant
   `if (payload.name.length > 128) throw …` check is dead code. Keep only business validation the
   FieldSchema cannot express.

5. **Constructing Value Objects inside the handler.** → Catalyst has no Value Object layer. The
   validation + modification concern is delegated to `FieldSchema` + type handlers via
   `@ApplySchema`. If a `Password` VO would have encapsulated bcrypt+masking, `type: 'password'`
   already does it.

6. **Calling `formatRecord` or `sanitizeRecord` directly instead of via a decorator.** → The
   decorator wires `handlerMeta.timezone`, `meta.contentLanguage`, and other options automatically.
   A direct call loses that context and produces inconsistent output. No exceptions remain — the
   pre-1.1.0 "manual format on included relation" workaround is obsolete (see anti-pattern #3).

## Naming Conventions

| Artifact          | Pattern                          | Example                   |
| ----------------- | -------------------------------- | ------------------------- |
| Field-schema file | `<pkg>-<module>.field-schema.ts` | `iam-tag.field-schema.ts` |
| Exported constant | `<Pkg><Module>FieldSchema`       | `IamTagFieldSchema`       |
| Folder placement  | `@app/<pkg>/<module>/domain/`    | `@app/iam/tag/domain/`    |

Placement, naming, and folder structure of every artifact — including where a field-schema sits
within the module tree — are covered by `catalyst-project-structure`.

## Cross-references

- **`catalyst-handler-composer`** — where the decorators land (handler `main()`). This skill
  explains the declarative layer; handler-composer explains how the handler orchestrates the call
  around it.
- **`catalyst-project-structure`** — folder layout (`@app/<bc>/<mod>/domain/` is where field-schemas
  live).
- **`catalyst-schema`** (monorepo root scope) — YAML `aggregateProperties` authoring, including
  `relationship` metadata that does NOT flow into the generated field-schema. Go there when you need
  to edit the YAML to change what the field-schema generator produces.
- **`catalyst-nestjs-primitives`** — catalyst's NestJS primitives: `@Auth` composite +
  `AuthenticationJwtGuard` + `AuthorizationPermissionsGuard`, upstream decorator catalog
  (`@Timezone`, `@Auditing`, `@CurrentAccount`, `@ContentLanguage`, `@TenantPolicy`,
  `@TenantConstraint`), the dual REST/GraphQL context pattern, and the `dPermissions.all`
  convention. Read this when writing or reasoning about guards, interceptors, pipes, filters, or
  custom decorators.
- **`catalyst-backend-testing`** — testing conventions for catalyst backend, including the critical
  gotcha that `@ApplySchema` / `@Format` / `@Sanitize` fire in `TestingModule` (a test's payload
  gets bcrypt-hashed before reaching the service mock; `@Format` masks return values). Required
  reading before writing any test that touches a handler decorated with these.
- **SPEC-07** in `aurora-catalyst-cli/ROADMAP.md` — ✅ completado (core-back@>=1.1.0 + CLI que
  emite entries `relation`). El pipeline recursa automáticamente por las relaciones declaradas.
  Cross-referenced from the "Relation formatting" section of `references/query-statement.md`.
