Sobre el proyecto

# @querry-kit/nest Helpers consolidados de NestJS para las APIs de Query Kit: proyección de campos, servicios de consulta estilo Prisma, políticas CASL, decoradores OpenAPI, pipes, DTOs de paginación y utilidades de objetos. ## 🌐 Ecosistema Querry Kit La [descripción general de Querry Kit](https://querry-kit.github.io/querry-kit/) conecta los tres paquetes principales: - [`@querry-kit/nest`](https://github.com/querry-kit/nest) proporciona la API de NestJS y los patrones de controlador. - [`@querry-kit/nuxt`](https://github.com/querry-kit/nuxt) proporciona clientes de API tipados y primitivas de datos headless para Vue/Nuxt. - [`@querry-kit/nuxt-ui`](https://github.com/querry-kit/nuxt-ui) proporciona controles de tabla de Nuxt UI construidos sobre esas primitivas. ## 📦 Instalación ```sh pnpm add @querry-kit/nest pnpm add @nestjs/common @nestjs/core @nestjs/swagger class-transformer class-validator reflect-metadata ``` Para el adaptador CASL opcional: ```sh pnpm add @casl/ability @casl/prisma ``` La versión actual del paquete se publica en npm. npm es el canal de distribución principal. Las etiquetas de release de GitHub siguen disponibles como alternativa: ```sh pnpm add github:querry-kit/nest#v0.0.1 ``` ## 🚀 Flujo de trabajo de releases Los releases se gestionan con Changesets y GitHub Actions. La rama `main` no contiene archivos `dist` confirmados; solo contiene el código fuente, el README y la configuración de los workflows. Los cambios visibles para el paquete deben incluir un changeset: ```sh pnpm changeset ``` Cuando los cambios llegan a `main`, el workflow `changesets` crea o actualiza un PR de release. Ese PR contiene el incremento de versión y las actualizaciones del changelog producidas por: ```sh pnpm changeset version ``` El workflow de publicación en npm usa npm Trusted Publishing a través de GitHub Actions OIDC. El paquete de npm debe estar conectado a este repositorio y workflow en la configuración de publicación del paquete de npm: - Repositorio: `querry-kit/nest` - Archivo de workflow: `release.yml` - Entorno: sin definir Después de fusionar el PR de release, el workflow `npm publish` se ejecuta para los cambios de versión/changelog. Ejecuta las comprobaciones del paquete, construye `dist` en CI, publica `@querry-kit/nest` en npm, etiqueta el commit de release como `vX.Y.Z` y crea un GitHub Release. Los consumidores deben instalar desde npm: ```sh pnpm add @querry-kit/nest ``` ## 🧩 Uso `ResourceQuery` cubre el flujo común de controlador: parsea `fields` opcionales, fusiona los includes requeridos por el endpoint con los valores de `include` del cliente, genera includes de Prisma para las relaciones seleccionadas, llama a un `QueryService`, mapea modelos a DTOs y proyecta la respuesta. ```ts import { Get, Query, Req } from '@nestjs/common'; import { ApiErrorResponses, ApiPaginatedResponse, ApiResourceQuery, QueryDTO, ResourceQuery } from '@querry-kit/nest'; @Get() @ApiResourceQuery() @ApiPaginatedResponse({ model: CustomerDTO }) @ApiErrorResponses({ badRequestDescription: 'Invalid query parameter.' }) async query(@Req() req: AuthRequest, @Query() query: QueryDTO<CustomerTypeMap>) { return ResourceQuery.query({ service: this.customersService, query, schema: CustomerDTO, ability: req.ability, map: (customer, ability) => CustomerDTO.fromModel(customer, ability), }); } ``` Crea servicios de recursos con `QueryService` y un delegado compatible con Prisma: ```ts import { Injectable } from '@nestjs/common'; import { QueryService, type BaseDelegateTypeMap } from '@querry-kit/nest'; import { Prisma, PrismaService } from '../prisma'; interface CustomerTypeMap extends BaseDelegateTypeMap { select: Prisma.CustomerSelect; include: Prisma.CustomerInclude; whereInput: Prisma.CustomerWhereInput; orderByWithRelationInput: Prisma.CustomerOrderByWithRelationInput; whereUniqueInput: Prisma.CustomerWhereUniqueInput; scalarFieldEnum: Prisma.CustomerScalarFieldEnum; } @Injectable() export class CustomersService extends QueryService<typeof PrismaService.prototype.customer, CustomerTypeMap> { constructor(prisma: PrismaService) { super(prisma.customer); } } ``` Hay subrutas específicas disponibles para importaciones más pequeñas: ```ts import { createCaslAccessibleWhere, filterCaslFields } from '@querry-kit/nest/casl'; import { ApiResourceQuery } from '@querry-kit/nest/decorators'; import { Fields } from '@querry-kit/nest/fields'; import { parseObject } from '@querry-kit/nest/object'; import { QueryService } from '@querry-kit/nest/query-service'; ``` ## 🔐 CASL CASL es opcional. Pasa `ability` solo cuando el endpoint deba fusionar una cláusula `where` con reconocimiento de autorización a través de `QueryService`; omítelo para APIs que no usan CASL. ```ts import { createCaslAccessibleWhere } from '@querry-kit/nest/casl'; super(prisma.customer, { subject: 'Customer', accessibleWhere: createCaslAccessibleWhere({ action: 'read' }), }); ``` Cuando se pasa una ability a `query`, el servicio combina la cláusula where de CASL con los filtros del usuario como `{ AND: [accessibleWhere, parsedWhere] }`. Para permisos de respuesta a nivel de campo, filtra el DTO completado sin mutarlo: ```ts return filterCaslFields(dto, 'Customer', ability); ``` El helper usa la acción `read` por defecto. Pasa `{ action: RoleAction.READ }` cuando una aplicación use acciones en mayúsculas o respaldadas por enum. Solo filtra campos de DTO serializados; sigue pasando la ability a `QueryService` para restringir también las lecturas de base de datos. ## 🎯 Fields `fields` es opcional por defecto. Cuando se omite, Query Kit devuelve la respuesta DTO completa. Cuando está presente, valida la sintaxis y los campos seleccionados, añade los includes de relación requeridos y proyecta los DTOs devueltos. Las llaves externas son opcionales, por lo que `{id,title}` es equivalente a `id,title`. ```txt GET /books?fields=id,title GET /books?fields={id,title} GET /books?fields=items{id,title},meta{page,perPage,itemCount,pageCount} GET /books?fields=items{},meta{page} ``` Un valor vacío explícito (`?fields=`) o una selección externa vacía (`?fields={}`) devuelve `{}`. Las selecciones anidadas vacías también son válidas: `items{}` produce objetos de item vacíos, mientras que `meta{page}` conserva solo los metadatos de página solicitados. Un valor que contenga solo espacios en blanco sigue siendo inválido. Usa `prepareFieldsQuery` directamente cuando un controlador necesite orquestación personalizada del servicio: ```ts import { Fields, prepareFieldsQuery } from '@querry-kit/nest/fields'; const prepared = prepareFieldsQuery({ fields: query.fields, include: query.include, schema: BookDTO, requiredInclude: { author: true }, }); const { items, pageMeta } = await booksService.query({ ...query, include: prepared.include }); return { items: Fields.project(items.map(BookDTO.fromModel), prepared.projection), meta: Fields.project(pageMeta, prepared.metaProjection), }; ``` ## 📖 Documentación - [Getting Started](https://querry-kit.github.io/querry-kit/docs/nest/guide/getting-started) - [Complete API Example](https://querry-kit.github.io/querry-kit/docs/nest/guide/example-app) - [CRUD Controller](https://querry-kit.github.io/querry-kit/docs/nest/guide/crud-controller) - [Fields](https://querry-kit.github.io/querry-kit/docs/nest/api/fields/) - [Query Service](https://querry-kit.github.io/querry-kit/docs/nest/api/query-service) - [DTOs and Pagination](https://querry-kit.github.io/querry-kit/docs/nest/api/dtos-pagination) - [OpenAPI Decorators](https://querry-kit.github.io/querry-kit/docs/nest/api/openapi) - [CASL](https://querry-kit.github.io/querry-kit/docs/nest/api/casl) - [API Reference](https://querry-kit.github.io/querry-kit/docs/nest/api/) ## 🛠 Desarrollo ```sh pnpm install pnpm lint pnpm check pnpm test pnpm test:coverage pnpm build ``` La lista de permitidos de compilación del workspace permite los scripts nativos requeridos por el conjunto de herramientas de pruebas y documentación, incluido el observador de archivos de Parcel. `pnpm test:coverage` recopila todos los archivos fuente, imprime el resumen de cobertura y escribe informes HTML y LCOV en `coverage/`. GitHub Actions ejecuta el mismo comando y conserva el informe como artefacto del workflow.