NestJS-YALC
Table of Contents:
  1. GraphQL input compatibility and rejection layers
    1. Canonical entity-aware inputs
    2. Retained compatibility APIs and consumer inventory
    3. Rejection policy

GraphQL input compatibility and rejection layers

Canonical entity-aware inputs

Pass the decorated entity or API model to sortModelFactory(Model), filterExpressionInputFactory(Model), crudGenParamsFactory(defaults, Model) and crudGenParamsNoPaginationFactory(defaults, Model). Generated resolvers already supply their selected API/entity model; join inputs pass the related model. The model’s mapped properties remain the single source of truth for the GraphQL field enum. This change does not widen that schema.

import { sortModelFactory, filterExpressionInputFactory } from
  '@nestjs-yalc/crud-gen/api-graphql/crud-gen.input.js';
import { crudGenParamsFactory } from '@nestjs-yalc/crud-gen/crud-gen.args.js';

const UserSort = sortModelFactory(UserType);
const UserFilters = filterExpressionInputFactory(UserType);
const UserArgs = crudGenParamsFactory(undefined, UserType);
const sorting = new UserSort();
sorting.colId = 'name'; // must be a key of UserType
const filters = new UserFilters();
filters.childExpressions = []; // recursively preserves UserType field keys

Factory return types now retain entity keys through cached results and recursive filter expressions. Existing broad interfaces remain available for generic service code; default-value arguments and dynamic extra parameters keep their compatibility typing. TypeScript keys are only an upper bound: undecorated properties, metadata aliases, hidden or derived fields may differ from the schema. Runtime metadata cannot manufacture a TypeScript enum or prove that a decorated field supports a repository operation. Use the emitted schema for client generation and validate operations against it. Runtime field decorators still use the metadata-generated enum, including text, number, date and set filters.

Retained compatibility APIs and consumer inventory

Surface Repository consumers Decision
SortModel (colId: string) Model-less args factories and input tests Exported deprecated class retained; prefer sortModelFactory(Model)
Model-less args factories Args tests; custom CrudGenArgs / CrudGenArgsNoPagination calls can omit entityType Retained compatibility overloads; prefer supplying a model
Entity-aware args factories Generated resolver decorators, related-model join inputs, args tests Canonical route
FilterScalar Model-less args factories and scalar tests Retained legacy JSON-string normalization route

This inventory covers checked-in source, not external applications. No public export or compatibility overload is removed. There is no scheduled removal; removing these paths needs a separately announced breaking release and external consumer assessment. A migration can change the public GraphQL input types, so regenerate clients and migrate callers together.

For a custom decorator, migrate CrudGenArgs({ ...options }) to CrudGenArgs({ ...options, entityType: UserType }). For direct factories, migrate crudGenParamsFactory(defaults) to crudGenParamsFactory(defaults, UserType) (and likewise for the no-pagination variant).

A legacy scalar variable is a JSON string containing a field-keyed filter map, for example:

{"filters":"{\"name\":{\"filterType\":\"text\",\"type\":\"contains\",\"filter\":\"Ada\"}}"}

The equivalent entity-aware variable uses the generated input object:

{"filters":{"expressions":[{"text":{"field":"name","type":"CONTAINS","filter":"Ada"}}]}}

The legacy scalar normalizes recognized field-keyed filters to expressions; it has no entity enum allowlist. It is not an arbitrary JSON-object scalar: non-string variable values currently normalize from an empty object; non-string literals are rejected. Malformed JSON fails parsing, and unrecognized entries may be ignored. Do not use it as field authorization or expect equivalent schema validation. Migrating only the TypeScript type does not migrate wire payloads. Both routes still depend on service/repository capabilities after coercion.

Rejection policy

Request validation/coercion and resolver execution are separate contracts. The skeleton configures Nest’s Apollo driver with Apollo Server 5 and default error formatting. Its SQLite GraphQL regressions pin these observations:

Fixture Layer HTTP Error contract
Unknown inline sorting.colId enum Document validation 400 GRAPHQL_VALIDATION_FAILED, no data or execution path
Unknown variable filter field enum Variable coercion 400 BAD_USER_INPUT, no data or execution path
Valid firstName filter with a plain repository Resolver capability rejection 200 INTERNAL_SERVER_ERROR, null data, grid execution path and capability message
Missing required extra arguments Resolver argument policy 200 BAD_REQUEST, null data, grid execution path and required-arguments message

A field omitted by schema metadata is not proof of repository rejection. The capability fixture therefore uses a schema-valid field, and unknown enum fixtures use a deliberately nonexistent key. These tests exercise different layers and must not accept alternate reasons or [200, 400] status ranges.

Other drivers, Apollo versions, content negotiation or explicitly configured error formatters can select different statuses and extension codes. GraphQL execution errors may accompany HTTP 200; validation/coercion need not share that status. Pin each supported configuration independently, preserving errors/no successful data and the intended rejection layer. Do not normalize all failures to HTTP 400 or change production exception handling to satisfy a test.

The schema regression builds real Nest GraphQL metadata for both factory modes. The type regression invokes TypeScript diagnostics directly against positive and @ts-expect-error cases, independent of ts-jest’s disabled diagnostics.