UNPKG

@elsikora/nestjs-crud-automator

Version:

A library for automating the creation of CRUD operations in NestJS.

203 lines (200 loc) 12.4 kB
import '../../../../enum/decorator/api/action.enum.js'; import '../../../../enum/decorator/api/authentication-type.enum.js'; import '../../../../enum/decorator/api/controller/get-list/query/filter/missing-behavior.enum.js'; import { EApiControllerGetListQueryPaginationMode } from '../../../../enum/decorator/api/controller/get-list/query/pagination-mode.enum.js'; import '../../../../enum/decorator/api/controller/get-list/query/unlisted-fields.enum.js'; import '../../../../enum/decorator/api/controller/relation-reference-shape.enum.js'; import '../../../../enum/decorator/api/controller/request/target.enum.js'; import '../../../../enum/decorator/api/controller/request/transformer-type.enum.js'; import '../../../../enum/decorator/api/controller/response-target.enum.js'; import { EApiDtoType } from '../../../../enum/decorator/api/dto-type.enum.js'; import '../../../../enum/decorator/api/function/context-storage-kind.enum.js'; import '../../../../enum/decorator/api/function/subscriber-transaction-expectation.enum.js'; import '../../../../enum/decorator/api/function/transaction/event-status.enum.js'; import '../../../../enum/decorator/api/function/transaction/failure-stage.enum.js'; import '../../../../enum/decorator/api/function/transaction/mode.enum.js'; import '../../../../enum/decorator/api/function/transaction/outcome.enum.js'; import '../../../../enum/decorator/api/function/transaction/owner-kind.enum.js'; import '../../../../enum/decorator/api/function/transaction/trace-type.enum.js'; import '../../../../enum/decorator/api/function/type.enum.js'; import '../../../../enum/decorator/api/on-type.enum.js'; import '../../../../enum/decorator/api/property/data-type.enum.js'; import '../../../../enum/decorator/api/property/date/identifier.enum.js'; import '../../../../enum/decorator/api/property/date/type.enum.js'; import '../../../../enum/decorator/api/property/desribe-type.enum.js'; import '../../../../enum/decorator/api/property/number-type.enum.js'; import '../../../../enum/decorator/api/property/string-type.enum.js'; import '../../../../enum/decorator/api/route/subscriber-authorization-expectation.enum.js'; import { EApiRouteType } from '../../../../enum/decorator/api/route/type.enum.js'; import { ApiControllerGetListQueryGetPaginationMode } from '../get-list/query/get-pagination-mode.utility.js'; import { ApiControllerIdentityPlanAssert } from '../identity/assert-plan.utility.js'; import { ApiControllerReadPlanAssert } from '../read/assert-plan.utility.js'; import '@nestjs/common'; import { CamelCaseString } from '../../../camel-case-string.utility.js'; import '../../../../constant/utility/dto/constant.js'; import { ErrorException } from '../../../error/exception.utility.js'; import { DtoGenerate } from '../../../dto/generate/core.utility.js'; import '@nestjs/swagger'; import '../../../../external/tslib/tslib.es6.js'; import '../../../../class/utility/dto/exception/details/foreign-key-violation.class.js'; import '../../../../class/utility/dto/exception/details/unique-violation.class.js'; import '../../../../enum/utility/manual-dto-property-metadata-decorator.enum.js'; import 'class-transformer'; import 'class-validator'; import 'lodash/random.js'; import '@nestjs/swagger/dist/constants.js'; import '../../../../validator/must-match-one-of-schemas.validator.js'; import '../../../../constant/interface/api/property/default-string-format.constant.js'; import '../../../../validator/is-regular-expression.validator.js'; import 'node:crypto'; import '../../../../enum/utility/exception-details-type.enum.js'; import '../../../../enum/filter/operand.enum.js'; import '../../../../enum/filter/operation.enum.js'; import '../../../../enum/filter/order-direction.enum.js'; import { DtoGenerateGetListCursorResponse } from '../../../dto/generate/get-list/cursor-response.utility.js'; import '../../../../constant/decorator/api/function.constant.js'; import '../../../../class/metadata-storage.class.js'; import 'typeorm'; import { DtoGenerateGetListResponse } from '../../../dto/generate/get-list/response.utility.js'; import 'reflect-metadata'; import '../../../../validator/all-or-none-of-listed-properties.validator.js'; import { DtoGenerateIdentityReadParameters } from '../../../dto/generate/identity-read-parameters.utility.js'; import { DtoGenerateReadParameters } from '../../../dto/generate/read-parameters.utility.js'; const getListItemResponseDtoCache = new Map(); const getListCursorItemResponseDtoCache = new WeakMap(); const getListCursorItemResponseDtoNameOwnerCache = new WeakMap(); /** * Resolves a DTO class for a generated controller route. * Prefers an explicitly configured DTO and falls back to auto-generation. * @param {IApiControllerProperties<E>} properties - Controller configuration * @param {IApiEntity<E>} entity - Entity metadata * @param {R} method - Route type * @param {EApiDtoType} dtoType - DTO kind to resolve * @param {TApiControllerPropertiesRoute<E, R>} routeConfig - Route-specific configuration * @param {IApiControllerGetListQueryPlan} [queryPlan] - Route-scoped plan for dynamic GET_LIST QUERY DTO generation. * @returns {Type<unknown> | undefined} -The resolved DTO class or undefined if no DTO is configured * @template E - Entity type * @template R - Route type */ function ApiControllerGetDto(properties, entity, method, dtoType, routeConfig, queryPlan) { ApiControllerReadPlanAssert(routeConfig); return ApiControllerGetDtoWithReadPlan(properties, entity, method, dtoType, routeConfig, queryPlan); } /** * Resolves a route DTO while applying an internal compiled read plan. * @template E - Entity type * @template R - Route type * @param {IApiControllerProperties<E>} properties - Controller properties * @param {IApiEntity<E>} entity - Entity metadata * @param {R} method - Route method * @param {EApiDtoType} dtoType - DTO kind to resolve * @param {TApiControllerPropertiesRoute<E, R>} routeConfig - Route-specific configuration * @param {IApiControllerGetListQueryPlan} [queryPlan] - Route-scoped GET_LIST QUERY plan. * @param {IApiControllerReadPlan} [readPlan] - Internal route-scoped PARAMETERS plan. * @param {IApiControllerIdentityPlan} [identityPlan] - Internal GET identity alias plan. * @returns {Type<unknown> | undefined} Resolved DTO class or undefined. */ function ApiControllerGetDtoWithReadPlan(properties, entity, method, dtoType, routeConfig, queryPlan, readPlan, identityPlan) { ApiControllerIdentityPlanAssert(routeConfig, identityPlan); if (dtoType === EApiDtoType.PARAMETERS) { ApiControllerReadPlanAssert(routeConfig, readPlan); } const configuredDto = routeConfig.dto?.[dtoType]; if (configuredDto) { if (method === EApiRouteType.GET_LIST && dtoType === EApiDtoType.RESPONSE && isGetListResponseDtoConfig(configuredDto)) { return getGetListItemResponseDto(properties.entity, entity, configuredDto, method, dtoType, queryPlan); } return configuredDto; } if (dtoType === EApiDtoType.PARAMETERS) { if (identityPlan) { return DtoGenerateIdentityReadParameters(entity, identityPlan, readPlan, routeConfig.autoDto?.[dtoType], routeConfig.security?.authentication?.guard); } if (readPlan) { return DtoGenerateReadParameters(entity, method, readPlan, routeConfig.autoDto?.[dtoType], routeConfig.security?.authentication?.guard); } } const isCursorGetListResponse = method === EApiRouteType.GET_LIST && dtoType === EApiDtoType.RESPONSE && ApiControllerGetListQueryGetPaginationMode(queryPlan) === EApiControllerGetListQueryPaginationMode.CURSOR; const effectiveQueryPlan = method === EApiRouteType.GET_LIST && (dtoType === EApiDtoType.QUERY || isCursorGetListResponse) ? queryPlan : undefined; return DtoGenerate(properties.entity, entity, method, dtoType, routeConfig.autoDto?.[dtoType], routeConfig.security?.authentication?.guard, effectiveQueryPlan); } /** * Builds a stable generated wrapper class name for GET_LIST custom item response DTOs. * @param {IApiEntity<E>} entity - Entity metadata used for the resource name. * @param {IApiControllerPropertiesRouteGetListResponseDtoConfig} config - Custom item response DTO config. * @param {EApiRouteType} method - Current route type. * @param {EApiDtoType} dtoType - Current DTO type. * @returns {string} Generated wrapper DTO class name. * @template E - Entity type. */ function buildGetListItemResponseDtoName(entity, config, method, dtoType) { return config.name ?? `${entity.name ?? "UnknownResource"}${CamelCaseString(method)}${CamelCaseString(dtoType)}${config.itemType.name}`; } /** * Generates or returns a cached list wrapper around a configured custom item response DTO. * @param {ObjectLiteral} resourceClass - Entity class used for wrapper metadata. * @param {IApiEntity<E>} entity - Entity metadata used for naming. * @param {IApiControllerPropertiesRouteGetListResponseDtoConfig} config - Custom item response DTO config. * @param {EApiRouteType} method - Current route type. * @param {EApiDtoType} dtoType - Current DTO type. * @param {IApiControllerGetListQueryPlan} [queryPlan] - Compiled pagination plan selecting the page or cursor wrapper. * @returns {Type<unknown>} Generated list wrapper DTO class. * @template E - Entity type. */ function getGetListItemResponseDto(resourceClass, entity, config, method, dtoType, queryPlan) { const isCursor = ApiControllerGetListQueryGetPaginationMode(queryPlan) === EApiControllerGetListQueryPaginationMode.CURSOR; const defaultName = buildGetListItemResponseDtoName(entity, config, method, dtoType); if (!isCursor) { const cacheKey = `${entity.name ?? "UnknownResource"}_${config.itemType.name}_${defaultName}`; const cached = getListItemResponseDtoCache.get(cacheKey); if (cached) { return cached; } // @ts-ignore The existing list wrapper generator accepts the entity constructor at runtime. const dto = DtoGenerateGetListResponse(resourceClass, config.itemType, defaultName); getListItemResponseDtoCache.set(cacheKey, dto); return dto; } const name = config.name ?? `${defaultName}Cursor`; const cacheKey = `${entity.name ?? "UnknownResource"}_${name}_cursor`; const nameOwnerKey = `${entity.name ?? "UnknownResource"}_${name}`; let nameOwnerCache = getListCursorItemResponseDtoNameOwnerCache.get(resourceClass); if (!nameOwnerCache) { nameOwnerCache = new Map(); getListCursorItemResponseDtoNameOwnerCache.set(resourceClass, nameOwnerCache); } const currentNameOwner = nameOwnerCache.get(nameOwnerKey); const hasPageConflict = [...getListItemResponseDtoCache.values()].some((dto) => dto.name === name); if (hasPageConflict || (currentNameOwner && currentNameOwner !== config.itemType)) { throw ErrorException(`GET_LIST item response DTO wrapper name "${name}" is already used by another item constructor or pagination mode; configure a unique response DTO name`); } nameOwnerCache.set(nameOwnerKey, config.itemType); let resourceCache = getListCursorItemResponseDtoCache.get(resourceClass); if (!resourceCache) { resourceCache = new WeakMap(); getListCursorItemResponseDtoCache.set(resourceClass, resourceCache); } let itemCache = resourceCache.get(config.itemType); if (!itemCache) { itemCache = new Map(); resourceCache.set(config.itemType, itemCache); } const cached = itemCache.get(cacheKey); if (cached) { return cached; } // @ts-ignore The existing list wrapper generators accept the entity constructor at runtime. const dto = DtoGenerateGetListCursorResponse(resourceClass, config.itemType, name); itemCache.set(cacheKey, dto); return dto; } /** * Returns whether a route response DTO config describes a custom GET_LIST item DTO. * @param {IApiControllerPropertiesRouteGetListResponseDtoConfig | Type<unknown>} value - Configured response DTO value. * @returns {boolean} Whether the value is the item DTO config shape. */ function isGetListResponseDtoConfig(value) { return typeof value === "object" && "itemType" in value && typeof value.itemType === "function"; } export { ApiControllerGetDto, ApiControllerGetDtoWithReadPlan }; //# sourceMappingURL=dto.utility.js.map