@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
203 lines (200 loc) • 12.4 kB
JavaScript
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