---
name: catalyst-nestjs-primitives
description: >
  Catalog and guide for NestJS primitives in the Aurora Catalyst backend: custom decorators, guards,
  interceptors, pipes, filters. Documents the three catalyst-authored primitives (`@Auth` composite
  decorator, `AuthenticationJwtGuard`, `AuthorizationPermissionsGuard`), references upstream
  primitives from `@aurorajs.dev/core-back` (`@Timezone`, `@Auditing`, `@CurrentAccount`,
  `@ContentLanguage`, `@TenantPolicy`, `@TenantConstraint`, `Permissions`), the dual REST/GraphQL
  context pattern via `Api.getRequestFromExecutionContext()`, and the `dPermissions.all` convention
  on IamAccount. Trigger: writing or reasoning about a guard, interceptor, pipe, filter, or custom
  decorator; protecting an endpoint with auth and permission checks; composing a custom composite
  decorator with `applyDecorators`; understanding why every catalyst guard overrides `getRequest`;
  understanding how `user.dPermissions.all` is read at guard time; deciding between `@Auth` and a
  custom guard. Fires even if the user says only "add auth to this endpoint" or "I need a guard for
  X". Does NOT apply to field-level validation (use `catalyst-field-schema`) and does NOT replace
  full-module audits (use `catalyst-review-module`).
license: MIT
metadata:
  author: aurora
  version: '1.0'
---

## Step 0 — Reuse-first Gate (Read Before Anything Else)

Before writing a new primitive, answer: **"is a custom primitive actually needed?"** In most cases
it is not — catalyst offers an upstream decorator, an `@Auth` composite, or a `FieldSchema` rule
that covers the intent. Primitives are hand-written (unlike handlers or field-schemas, which the CLI
generates), so the gate here is NOT "do not bypass the generator" but **"do not duplicate an
existing primitive or reinvent `@Auth`"**.

Route every primitive task through one of these four branches.

```
Need a NestJS primitive?
  │
  ├── Does `@aurorajs.dev/core-back` already export it?   ───→ USE UPSTREAM
  │   (@Timezone, @Auditing, @CurrentAccount,
  │    @ContentLanguage, @TenantPolicy, @TenantConstraint, …)
  │
  ├── Is the task "protect an endpoint with auth +       ───→ USE @Auth('<pkg>.<mod>.<op>')
  │   permission checks"?                                    (composite: Permissions +
  │                                                           AuthenticationJwtGuard +
  │                                                           AuthorizationPermissionsGuard)
  │
  ├── Is it actually field-level validation / formatting?───→ catalyst-field-schema
  │   (maxLength, nullable, enum, timezone, password            (@ApplySchema, @Format,
  │    hashing, format masking, …)                              @Sanitize, `rules`)
  │
  └── Truly new primitive needed?                         ───→ references/minimum-templates.md
       (domain-specific orchestration, a cross-cutting          (and the dual-context
        concern not covered above)                              override is non-negotiable
                                                                for guards / interceptors)
```

Only when the first three branches do NOT apply does the fourth branch fire — and even then, the
template you copy must respect catalyst's dual REST/GraphQL context pattern (see below).

## When to Use

- Writing a new guard, interceptor, pipe, exception filter, or custom decorator in catalyst backend.
- Reasoning about an existing primitive (`@Auth`, `AuthenticationJwtGuard`,
  `AuthorizationPermissionsGuard`) or deciding whether to extend it vs write a new one.
- Choosing between `@Auth('<permission>')` and a handcrafted `@UseGuards(...)` — the answer is
  always `@Auth`, and this skill explains why.
- Understanding the `dPermissions.all` shape consumed by permission-aware guards.
- Understanding why catalyst guards override `getRequest()` and what breaks when they do not.

This skill is **REFERENCE / CATALOG** — it inventories what exists, routes creation through a
reuse-first gate, and provides templates only as a last resort.

## Inventory — Catalyst-authored primitives

| Primitive                       | Type                | Purpose                                                                                                                                                                                                                                                                                                 | Path                                                                            |
| ------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `@Auth(...permissions)`         | Composite decorator | Protects an endpoint via `Permissions(...)` metadata + `UseGuards(AuthenticationJwtGuard, AuthorizationPermissionsGuard)`. Canonical endpoint-protection decorator used on every resolver / controller.                                                                                                 | `backend/src/@aurora/decorators/auth.decorator.ts`                              |
| `AuthenticationJwtGuard`        | Guard               | Extends passport's `AuthGuard('jwt')`. Overrides `getRequest()` via `Api.getRequestFromExecutionContext(context)` so it works in both REST (HTTP context) and GraphQL (execution context). Throws `UnauthorizedException` with `statusCode: 401` when the JWT is missing or invalid.                    | `backend/src/@app/authorization-code/guards/authentication-jwt.guard.ts`        |
| `AuthorizationPermissionsGuard` | Guard               | Reads the `permissions` reflection metadata set by `Permissions(...)` (i.e., by `@Auth(...)`) and checks if the authenticated user's `dPermissions.all` intersects the required set via `Arr.intersects`. Same dual-context pattern. Throws `UnauthorizedException` with `statusCode: 403` on mismatch. | `backend/src/@app/authorization-code/guards/authorization-permissions.guard.ts` |

That is the entire catalyst-authored primitive surface on disk today: one composite decorator and
two guards. No custom pipes, interceptors, filters, or middleware exist in the repo. If a new
pattern needs expressing, it is more likely a FieldSchema concern or belongs behind `@Auth` than a
fresh primitive.

## Inventory — Upstream primitives (`@aurorajs.dev/core-back`)

> **Snapshot as of change-archival time.** This is a catalog, not documentation. Upstream semantics
> may evolve; the canonical reference is the source package. When creating any custom primitive,
> re-check the upstream package index first — a new decorator may have landed since this snapshot.

| Primitive               | Purpose (one line)                                                                                                                                                                   | Source                                   |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| `@Timezone()`           | Param decorator — extracts timezone from the request headers / context.                                                                                                              | `@aurorajs.dev/core-back` (`decorators`) |
| `@Auditing()`           | Param decorator — extracts auditing metadata (user id, timestamp, correlation id).                                                                                                   | `@aurorajs.dev/core-back` (`decorators`) |
| `@CurrentAccount()`     | Param decorator — extracts the authenticated account object from the request.                                                                                                        | `@aurorajs.dev/core-back` (`decorators`) |
| `@ContentLanguage()`    | Param decorator — extracts the content-language preference from the request.                                                                                                         | `@aurorajs.dev/core-back` (`decorators`) |
| `@TenantPolicy()`       | Method / class decorator — enforces tenant write scope (use on mutations).                                                                                                           | `@aurorajs.dev/core-back` (`decorators`) |
| `@TenantConstraint()`   | Method / class decorator — enforces tenant read scope (use on queries).                                                                                                              | `@aurorajs.dev/core-back` (`decorators`) |
| `Permissions(...names)` | Metadata decorator — sets the `permissions` reflection key read by `AuthorizationPermissionsGuard`. Rarely used directly; prefer `@Auth(...)` composite which wires it consistently. | `@aurorajs.dev/core-back` (`decorators`) |

Everything not listed here that a catalyst resolver / controller uses (`@Args`, `@Body`,
`@Mutation`, `@Query`, `@Resolver`, `@Controller`, `@Post`, `@Get`, `@Put`, `@Delete`, …) is
standard NestJS and covered by the NestJS documentation. Do not duplicate NestJS docs here.

## Dual REST/GraphQL Context Pattern (Catalyst-specific)

Catalyst exposes every module over both REST and GraphQL. A guard, interceptor, or param decorator
that reads the request MUST work in both contexts. NestJS's default behavior in `CanActivate` /
`NestInterceptor` / `createParamDecorator` assumes an HTTP execution context and returns the wrong
object (or throws) under GraphQL.

The fix is a single helper from `@aurorajs.dev/core-back`:

```typescript
Api.getRequestFromExecutionContext(context: ExecutionContext): Request
```

It inspects the context type and returns the correct request object for either REST or GraphQL.
Every catalyst guard overrides `getRequest()` to call it. The real shape, from
`backend/src/@app/authorization-code/guards/authentication-jwt.guard.ts`:

```typescript
import { Api } from '@aurorajs.dev/core-back';
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class AuthenticationJwtGuard extends AuthGuard('jwt') {
  getRequest<Request>(context: ExecutionContext): Request {
    return Api.getRequestFromExecutionContext(context);
  }

  handleRequest<AccountResponse>(err: any, user: AccountResponse): AccountResponse {
    if (err || !user) {
      throw (
        err ||
        new UnauthorizedException({
          statusCode: 401,
          message: 'OAuthAccessToken not allowed',
        })
      );
    }
    return user;
  }
}
```

`AuthorizationPermissionsGuard` follows the same pattern (see its path above). Any NEW custom guard
or interceptor in catalyst MUST include the override — otherwise the primitive silently works in one
API style and breaks in the other. This is the single most important rule in this skill.

## `dPermissions.all` Convention (Catalyst-specific)

Permission-aware guards in catalyst read the authenticated user's permissions from a pre-computed
array on the `IamAccount` entity:

```typescript
const accountPermissions = this.getRequest(context).user.dPermissions.all;
```

**Shape** — `dPermissions` is a jsonb field on IamAccount (`iam-account.field-schema.ts`:
`{ name: 'dPermissions', type: 'jsonb', nullable: false }`). Its Sequelize model types it as `any`;
at runtime the shape is `{ all: string[] }` where `.all` is the flattened array of every permission
string the account has.

**Why the `d` prefix** — stands for **denormalized**. The field caches permissions derived from the
account's roles so permission-aware guards can read it in O(1) without traversing relations at guard
time. How `dPermissions` is populated is an iam/account concern (sagas, role-change event handlers)
and out of scope for this skill.

**How guards consume it** — `AuthorizationPermissionsGuard` reads `user.dPermissions.all` and
compares against the required permissions via `Arr.intersects(required, user.dPermissions.all)` from
`@aurorajs.dev/core-common`. Any custom permission-aware primitive you write MUST follow the same
pattern. Do not traverse roles. Do not read a different field. Use `Arr.intersects` for the check —
it handles edge cases (empty arrays, non-array inputs) consistently.

## Anti-patterns

1. **Writing a pipe for field-level validation.** → Use `@ApplySchema(FieldSchema)` from
   `catalyst-field-schema`. The schema's `maxLength`, `nullable`, `enumOptions`, and `rules` already
   enforce what a pipe would re-implement. A pipe duplicates the check and drifts from the
   declarative contract.

2. **Writing an interceptor for audit logging.** → Use the upstream `@Auditing()` decorator on the
   resolver / controller parameter. It extracts user id, timestamp, and correlation id consistently;
   an interceptor has to reinvent that extraction and reconcile with
   `HandlerMeta.repositoryOptions.auditing`.

3. **Writing a guard for authentication / authorization.** → Use `@Auth('<pkg>.<mod>.<op>')`
   composite. It applies `Permissions(...)` metadata + both guards in the correct order. A
   hand-rolled guard skips the metadata hook and breaks permission checks silently.

4. **Writing a custom guard or interceptor WITHOUT overriding `getRequest()` via
   `Api.getRequestFromExecutionContext(context)`.** → The primitive silently works in only one API
   style. If it reads from the HTTP request, it fails on GraphQL queries; vice versa. Every catalyst
   guard / interceptor that touches the request MUST override.

5. **Hardcoding permission strings in `@UseGuards(...)` directly.** → Use `@Auth('<permission>')`
   composite so `Permissions(...)` metadata + both guards are wired consistently. Manually wiring
   `@UseGuards(AuthenticationJwtGuard, AuthorizationPermissionsGuard)` + `Permissions(...)`
   separately is noise that drifts across files.

6. **Re-documenting upstream decorators inside this skill's references.** → The upstream catalog
   above is a SNAPSHOT with source pointers, not inline documentation. Upstream semantics may
   evolve; re-documenting creates drift. When a new upstream decorator is worth knowing about, add a
   ROW to the catalog — do not copy its implementation notes.

## Naming Conventions

| Primitive type             | File name pattern       | Placement                                                                              |
| -------------------------- | ----------------------- | -------------------------------------------------------------------------------------- |
| Guard                      | `<name>.guard.ts`       | `@app/<bc>/<mod>/guards/` (module-specific) or `@aurora/guards/` (cross-cutting, rare) |
| Interceptor                | `<name>.interceptor.ts` | `@app/<bc>/<mod>/interceptors/` or `@aurora/interceptors/`                             |
| Pipe                       | `<name>.pipe.ts`        | `@app/<bc>/<mod>/pipes/` or `@aurora/pipes/`                                           |
| Exception filter           | `<name>.filter.ts`      | `@aurora/filters/` (exception filters are typically cross-cutting)                     |
| Custom composite decorator | `<name>.decorator.ts`   | `@aurora/decorators/` (cross-cutting — see `@Auth`)                                    |
| Custom param decorator     | `<name>.decorator.ts`   | `@aurora/decorators/` (cross-cutting) or module-specific as needed                     |

The dot-type suffix is the NestJS convention and aligns with how `catalyst-review-module` audits
naming coherence.

## Cross-references

- **`catalyst-handler-composer`** — where `@Auth`, `@Timezone`, `@Auditing` land (on resolvers and
  controllers). Handler-composer references this skill back; the two are complementary.
- **`catalyst-field-schema`** — the destination for any "pipe for field validation" redirection. If
  the task is about a field's `maxLength`, `nullable`, `enumOptions`, or `rules`, go there.
- **`catalyst-project-structure`** — where new primitive files go physically (`@aurora/decorators/`
  for cross-cutting composites, `@app/<bc>/<mod>/guards/` for module-scoped guards, etc.).
- **`catalyst-backend-testing`** — testing guards and interceptors including the dual-context
  pattern: test doubles MUST fake both `'http'` and `'graphql'` execution context types to exercise
  the `Api.getRequestFromExecutionContext()` override in both branches. Covers `TestingModule`
  setup, mocking request shape, and the e2e supertest + seeded `dPermissions.all` pattern for
  `@Auth`-protected endpoint tests.
