diff --git a/apps/ebitemp-api/src/app/app.module.ts b/apps/ebitemp-api/src/app/app.module.ts index ba1b4c5..374fc4f 100644 --- a/apps/ebitemp-api/src/app/app.module.ts +++ b/apps/ebitemp-api/src/app/app.module.ts @@ -9,6 +9,7 @@ import { AppFileTransactionsModule } from './modules/file-transactions/file-tran import { AppHealthModule } from './modules/health/health.module'; import { AppLoggerModule } from './modules/logger/logger.module'; import { AppScheduleModule } from './modules/schedule/schedule.module'; +import { AppValidationModule } from './modules/validation/validation.module'; @Module({ imports: [ @@ -18,6 +19,7 @@ import { AppScheduleModule } from './modules/schedule/schedule.module'; AppHealthModule, AppFileTransactionsModule, AppScheduleModule, + AppValidationModule, EnumifyModule, ], controllers: [AppController], diff --git a/apps/ebitemp-api/src/app/modules/validation/utils/nestjs-swagger-patches.ts b/apps/ebitemp-api/src/app/modules/validation/utils/nestjs-swagger-patches.ts new file mode 100644 index 0000000..41a2a1b --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/validation/utils/nestjs-swagger-patches.ts @@ -0,0 +1,4 @@ + +export async function patchNestjsSwagger() { + (await import('@anatine/zod-nestjs')).patchNestjsSwagger(); +}; diff --git a/apps/ebitemp-api/src/app/modules/validation/validation.module.ts b/apps/ebitemp-api/src/app/modules/validation/validation.module.ts new file mode 100644 index 0000000..3133968 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/validation/validation.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; +import { ZodValidationPipe } from './zod.pipe'; +import { ZodSerializerInterceptor } from './zod.serializer'; + +@Module({ + providers: [ + { provide: APP_PIPE, useClass: ZodValidationPipe }, + { provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor }, + ], +}) +export class AppValidationModule {} diff --git a/apps/ebitemp-api/src/app/modules/validation/zod.pipe.ts b/apps/ebitemp-api/src/app/modules/validation/zod.pipe.ts new file mode 100644 index 0000000..c118c73 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/validation/zod.pipe.ts @@ -0,0 +1,36 @@ +import { ZodDtoStatic } from '@anatine/zod-nestjs'; +import { HTTP_ERRORS_BY_CODE } from '@anatine/zod-nestjs/src/lib/http-errors'; + +import { ArgumentMetadata, HttpStatus, Injectable, Optional, PipeTransform } from '@nestjs/common'; + +export interface ZodValidationPipeOptions { + errorHttpStatusCode?: keyof typeof HTTP_ERRORS_BY_CODE; +} + +@Injectable() +export class ZodValidationPipe implements PipeTransform { + private readonly errorHttpStatusCode: keyof typeof HTTP_ERRORS_BY_CODE; + + constructor(@Optional() options?: ZodValidationPipeOptions) { + this.errorHttpStatusCode = options?.errorHttpStatusCode || HttpStatus.BAD_REQUEST; + } + + public transform(value: unknown, metadata: ArgumentMetadata): unknown { + const zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema; + + if (zodSchema) { + const parseResult = zodSchema.safeParse(value); + + if (!parseResult.success) { + const { error } = parseResult; + const message = error.errors.map((error) => `${error.path.join('.')}: ${error.message}`); + + throw new HTTP_ERRORS_BY_CODE[this.errorHttpStatusCode](message); + } + + return parseResult.data; + } + + return value; + } +} diff --git a/apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts b/apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts new file mode 100644 index 0000000..5cd71aa --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts @@ -0,0 +1,87 @@ +import { ZodDtoStatic } from '@anatine/zod-nestjs'; +import { HTTP_ERRORS_BY_CODE } from '@anatine/zod-nestjs/src/lib/http-errors'; +import { + CallHandler, + ExecutionContext, + HttpStatus, + Inject, + Injectable, + NestInterceptor, + Optional, + SetMetadata, + StreamableFile, +} from '@nestjs/common'; +import { Observable, map } from 'rxjs'; +import { ZodSchema } from 'zod'; + +const REFLECTOR = 'Reflector'; + +export interface ZodSerializerInterceptorOptions { + errorHttpStatusCode?: keyof typeof HTTP_ERRORS_BY_CODE; +} + +export const ZodSerializerDto = (dto: ZodDtoStatic | ZodSchema) => SetMetadata('zodSerializedDtoOptions', { dto }); + +@Injectable() +export class ZodSerializerInterceptor implements NestInterceptor { + private readonly errorHttpStatusCode: keyof typeof HTTP_ERRORS_BY_CODE; + + constructor( + @Inject(REFLECTOR) protected readonly reflector: any, + @Optional() options?: ZodSerializerInterceptorOptions + ) { + this.errorHttpStatusCode = options?.errorHttpStatusCode || HttpStatus.INTERNAL_SERVER_ERROR; + } + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const { dto: responseSchema } = this.getContextResponseSchema(context); + + return next.handle().pipe( + map((res: object | object[]) => { + if (!responseSchema) return res; + if (typeof res !== 'object' || res instanceof StreamableFile) return res; + + return Array.isArray(res) + ? res.map( + (item) => + validateInterally(responseSchema, item, { httpStatusCodeOnError: this.errorHttpStatusCode }).data + ) + : validateInterally(responseSchema, res, { httpStatusCodeOnError: this.errorHttpStatusCode }).data; + }) + ); + } + + protected getContextResponseSchema(context: ExecutionContext): { + dto: ZodDtoStatic | ZodSchema | undefined; + } { + return this.reflector.getAllAndMerge('zodSerializedDtoOptions', [context.getHandler(), context.getClass()]); + } +} + +function isZodDto(metatype: unknown): metatype is ZodDtoStatic { + return !!(metatype as ZodDtoStatic)?.zodSchema; +} + +function validateInterally( + schemaOrDto: ZodDtoStatic | ZodSchema, + value: any, + options: Partial<{ + httpStatusCodeOnError: keyof typeof HTTP_ERRORS_BY_CODE; + }> +) { + const { httpStatusCodeOnError = HttpStatus.INTERNAL_SERVER_ERROR } = options; + let schema = isZodDto(schemaOrDto) ? schemaOrDto.zodSchema : schemaOrDto; + + const parseResult = schema.safeParse(value); + if (!parseResult.success) { + const { error } = parseResult; + const message = error.errors.map((error) => `${error.path.join('.')}: ${error.message}`); + + console.error( + `Error while validating dto '${isZodDto(schemaOrDto) ? schemaOrDto.name : 'unknownSchema'}': ${message}` + ); + console.error('value', value); + throw new HTTP_ERRORS_BY_CODE[httpStatusCodeOnError](); + } + return parseResult; +} diff --git a/apps/ebitemp-api/src/main.ts b/apps/ebitemp-api/src/main.ts index ddea903..8535f98 100644 --- a/apps/ebitemp-api/src/main.ts +++ b/apps/ebitemp-api/src/main.ts @@ -1,17 +1,17 @@ import { Logger, RequestMethod } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { - FastifyAdapter, - NestFastifyApplication, -} from '@nestjs/platform-fastify'; +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { Logger as PinoLogger } from 'nestjs-pino'; import { AppModule } from './app/app.module'; import { LocalConfig, localConfig } from './app/modules/config/local.config'; import { patchTypeOrm } from './app/modules/database/utils/typeorm-patch'; +import { patchNestjsSwagger } from './app/modules/validation/utils/nestjs-swagger-patches'; +import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { await patchTypeOrm(); + await patchNestjsSwagger(); const appOpts = (() => { const loggerOpts = { bufferLogs: true }; @@ -20,10 +20,7 @@ async function bootstrap() { ...loggerOpts, }; })(); - const app = await NestFactory.create( - AppModule, - new FastifyAdapter(appOpts) - ); + const app = await NestFactory.create(AppModule, new FastifyAdapter(appOpts)); const globalPrefix = 'v1'; app.setGlobalPrefix(globalPrefix, { exclude: [{ path: 'health', method: RequestMethod.GET }], @@ -35,10 +32,26 @@ async function bootstrap() { app.useLogger(app.get(PinoLogger)); + const swaggerConfig = new DocumentBuilder() + .addBearerAuth() + .build(); + const swaggerOptions = { + operationIdFactory: (controllerKey: string, methodKey: string) => { + return `${controllerKey}_${methodKey}`; + }, + } satisfies SwaggerDocumentOptions; + const document = SwaggerModule.createDocument(app, swaggerConfig, swaggerOptions); + SwaggerModule.setup(`${globalPrefix}/docs`, app, document, { + swaggerOptions: { + operationsSorter: 'alpha', + displayOperationId: true, + filter: true, + persistAuthorization: true, + }, + }); + await app.listen(port); - Logger.log( - `🚀 Application is running on: http://localhost:${port}/${globalPrefix}` - ); + Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`); } void bootstrap(); diff --git a/package.json b/package.json index b65fff8..8be0939 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,11 @@ "private": true, "dependencies": { "10": "^0.0.1", + "@anatine/zod-nestjs": "^2.0.10", + "@anatine/zod-openapi": "^2.2.7", "@autotelic/pino-seq-transport": "^0.1.0", + "@fastify/send": "^4.0.0", + "@fastify/static": "^8.1.0", "@keyv/redis": "^4.2.0", "@nestjs/common": "^11.0.8", "@nestjs/config": "^4.0.0", @@ -19,6 +23,7 @@ "@nestjs/platform-express": "^11.0.8", "@nestjs/platform-fastify": "^11.0.8", "@nestjs/schedule": "^5.0.1", + "@nestjs/swagger": "^11.0.3", "@nestjs/terminus": "^11.0.0", "@nestjs/typeorm": "^11.0.0", "@nx/devkit": "20.4.2", @@ -30,6 +35,7 @@ "flatted": "^3.3.2", "lodash": "^4.17.21", "nestjs-pino": "^4.3.0", + "openapi3-ts": "^4.4.0", "patch-package": "^8.0.0", "pino-http": "^10.4.0", "pino-pretty": "^13.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d90b6f2..0cbdea8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,21 @@ dependencies: '10': specifier: ^0.0.1 version: 0.0.1 + '@anatine/zod-nestjs': + specifier: ^2.0.10 + version: 2.0.10(@anatine/zod-openapi@2.2.7)(@nestjs/common@11.0.8)(@nestjs/swagger@11.0.3)(openapi3-ts@4.4.0)(zod@3.24.1) + '@anatine/zod-openapi': + specifier: ^2.2.7 + version: 2.2.7(openapi3-ts@4.4.0)(zod@3.24.1) '@autotelic/pino-seq-transport': specifier: ^0.1.0 version: 0.1.0 + '@fastify/send': + specifier: ^4.0.0 + version: 4.0.0 + '@fastify/static': + specifier: ^8.1.0 + version: 8.1.0 '@keyv/redis': specifier: ^4.2.0 version: 4.2.0 @@ -28,10 +40,13 @@ dependencies: version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) '@nestjs/platform-fastify': specifier: ^11.0.8 - version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) + version: 11.0.8(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) '@nestjs/schedule': specifier: ^5.0.1 version: 5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) + '@nestjs/swagger': + specifier: ^11.0.3 + version: 11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: ^11.0.0 version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20) @@ -65,6 +80,9 @@ dependencies: nestjs-pino: specifier: ^4.3.0 version: 4.3.0(@nestjs/common@11.0.8)(pino-http@10.4.0) + openapi3-ts: + specifier: ^4.4.0 + version: 4.4.0 patch-package: specifier: ^8.0.0 version: 8.0.0 @@ -224,6 +242,34 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true + /@anatine/zod-nestjs@2.0.10(@anatine/zod-openapi@2.2.7)(@nestjs/common@11.0.8)(@nestjs/swagger@11.0.3)(openapi3-ts@4.4.0)(zod@3.24.1): + resolution: {integrity: sha512-90TVowUCqlz7IwNf6EkrtHSTILeSwFnwWIhVDIEYPCdgfO+vVkASiT8RO+Q/olwuo3OxNef9fBDimIDxZMWzcg==} + peerDependencies: + '@anatine/zod-openapi': ^2.0.1 + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/swagger': ^6.0.0 || ^7.0.0 + openapi3-ts: ^4.1.2 + zod: ^3.20.0 + dependencies: + '@anatine/zod-openapi': 2.2.7(openapi3-ts@4.4.0)(zod@3.24.1) + '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/swagger': 11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2) + openapi3-ts: 4.4.0 + ts-deepmerge: 6.2.1 + zod: 3.24.1 + dev: false + + /@anatine/zod-openapi@2.2.7(openapi3-ts@4.4.0)(zod@3.24.1): + resolution: {integrity: sha512-kv/bGowgSGHNY2d/KIzx941ym0/elc7xoBiPri31qEUqbDPOSIppiMOZ88AedaTtLk5J1K96++h0CEsHkgFFyQ==} + peerDependencies: + openapi3-ts: ^4.1.2 + zod: ^3.20.0 + dependencies: + openapi3-ts: 4.4.0 + ts-deepmerge: 6.2.1 + zod: 3.24.1 + dev: false + /@angular-devkit/core@16.0.1: resolution: {integrity: sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==} engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -1818,6 +1864,10 @@ packages: levn: 0.4.1 dev: true + /@fastify/accept-negotiator@2.0.1: + resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} + dev: false + /@fastify/ajv-compiler@4.0.2: resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} dependencies: @@ -1876,6 +1926,37 @@ packages: ipaddr.js: 2.2.0 dev: false + /@fastify/send@3.3.1: + resolution: {integrity: sha512-6pofeVwaHN+E/MAofCwDqkWUliE3i++jlD0VH/LOfU8TJlCkMUSgKvA9bawDdVXxjve7XrdYMyDmkiYaoGWEtA==} + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + dev: false + + /@fastify/send@4.0.0: + resolution: {integrity: sha512-eJjKDxyBnZ1iMHcmwYWG5wSA/yzVY/yrBy3Upd2+hc0omcK13tWeXRcbF28zEcbl+Z2kXEgMzJ5Rb/gXGWx9Rg==} + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + dev: false + + /@fastify/static@8.1.0: + resolution: {integrity: sha512-lPb8+1ulvbGSUSQ0/adBDyp/Ye/MX+pBwhkLAr8/GU88kNnJlSu7KXdyW6CCOROcr5BgrqJD01lEOosozFAegw==} + dependencies: + '@fastify/accept-negotiator': 2.0.1 + '@fastify/send': 3.3.1 + content-disposition: 0.5.4 + fastify-plugin: 5.0.1 + fastq: 1.19.0 + glob: 11.0.1 + dev: false + /@humanfs/core@0.19.1: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2242,6 +2323,15 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + /@lukeed/ms@2.0.2: + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + dev: false + + /@microsoft/tsdoc@0.15.0: + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + dev: false + /@mole-inc/bin-wrapper@8.0.1: resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2493,6 +2583,23 @@ packages: tslib: 2.8.1 uid: 2.0.2 + /@nestjs/mapped-types@2.1.0(@nestjs/common@11.0.8)(reflect-metadata@0.2.2): + resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + dev: false + /@nestjs/platform-express@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): resolution: {integrity: sha512-Ru7seOYYglKNGQFzNALE5ilLqkdtX/ge6AJDKLMt+WI7iElZ7lXjT40fE3+HVUiZODunmeKQ7jVxcQyZwLafVA==} peerDependencies: @@ -2509,7 +2616,7 @@ packages: transitivePeerDependencies: - supports-color - /@nestjs/platform-fastify@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): + /@nestjs/platform-fastify@11.0.8(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): resolution: {integrity: sha512-LGQCMFJB4oaZjLLWeK+DdTdVUwkGafL6/89XvZ9+UF3S+NvGVp/gerIw+EjDPOj4kwT2Cvh6TwuydF2St1PGtQ==} peerDependencies: '@fastify/static': ^8.0.0 @@ -2525,6 +2632,7 @@ packages: '@fastify/cors': 10.0.2 '@fastify/formbody': 8.0.2 '@fastify/middie': 9.0.3 + '@fastify/static': 8.1.0 '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) fastify: 5.2.1 @@ -2573,6 +2681,35 @@ packages: - chokidar dev: true + /@nestjs/swagger@11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2): + resolution: {integrity: sha512-oyrhrAzVJz1wYefIYDb6Y0f1VYb8BtYxEI7Ex0ApoUsfGZThyhW9elYANcfBXVaTmICrU8lCESF2ygF6s0ThIw==} + peerDependencies: + '@fastify/static': ^8.0.0 + '@nestjs/common': ^11.0.1 + '@nestjs/core': ^11.0.1 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@fastify/static': 8.1.0 + '@microsoft/tsdoc': 0.15.0 + '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.1.0(@nestjs/common@11.0.8)(reflect-metadata@0.2.2) + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 8.2.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.18.2 + dev: false + /@nestjs/terminus@11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20): resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} peerDependencies: @@ -3407,6 +3544,11 @@ packages: '@redis/client': 1.6.0 dev: false + /@scarf/scarf@1.4.0: + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + requiresBuild: true + dev: false + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -5224,7 +5366,6 @@ packages: engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 - dev: true /content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} @@ -6700,6 +6841,19 @@ packages: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + /glob@11.0.1: + resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} + engines: {node: 20 || >=22} + hasBin: true + dependencies: + foreground-child: 3.3.0 + jackspeak: 4.0.2 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + dev: false + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -7312,6 +7466,13 @@ packages: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + /jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + engines: {node: 20 || >=22} + dependencies: + '@isaacs/cliui': 8.0.2 + dev: false + /jake@10.9.2: resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} engines: {node: '>=10'} @@ -7764,7 +7925,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} @@ -8100,6 +8260,11 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + /lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + engines: {node: 20 || >=22} + dev: false + /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -8248,6 +8413,12 @@ packages: hasBin: true dev: true + /mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -8276,6 +8447,13 @@ packages: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: true + /minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + dependencies: + brace-expansion: 2.0.1 + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -8652,6 +8830,12 @@ packages: is-docker: 2.2.1 is-wsl: 2.2.0 + /openapi3-ts@4.4.0: + resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==} + dependencies: + yaml: 2.7.0 + dev: false + /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -8910,6 +9094,14 @@ packages: lru-cache: 10.4.3 minipass: 7.1.2 + /path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + dependencies: + lru-cache: 11.0.2 + minipass: 7.1.2 + dev: false + /path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} dev: true @@ -10509,6 +10701,12 @@ packages: picocolors: 1.1.1 dev: true + /swagger-ui-dist@5.18.2: + resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} + dependencies: + '@scarf/scarf': 1.4.0 + dev: false + /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -10694,6 +10892,11 @@ packages: typescript: 5.7.3 dev: true + /ts-deepmerge@6.2.1: + resolution: {integrity: sha512-8CYSLazCyj0DJDpPIxOFzJG46r93uh6EynYjuey+bxcLltBeqZL7DMfaE5ZPzZNFlav7wx+2TDa/mBl8gkTYzw==} + engines: {node: '>=14.13.1'} + dev: false + /ts-jest@29.2.5(@babel/core@7.26.7)(jest@29.7.0)(typescript@5.7.3): resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}