@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
151 lines (147 loc) • 9.23 kB
JavaScript
;
var node_crypto = require('node:crypto');
require('../../../constant/decorator/api/function.constant.js');
var propertyDescribe_constant = require('../../../constant/decorator/api/property-describe.constant.js');
var safeObjectPropertyNames_constant = require('../../../constant/safe-object-property-names.constant.js');
require('../../../enum/decorator/api/action.enum.js');
require('../../../enum/decorator/api/authentication-type.enum.js');
require('../../../enum/decorator/api/controller/get-list/query/filter/missing-behavior.enum.js');
require('../../../enum/decorator/api/controller/get-list/query/pagination-mode.enum.js');
require('../../../enum/decorator/api/controller/get-list/query/unlisted-fields.enum.js');
require('../../../enum/decorator/api/controller/relation-reference-shape.enum.js');
require('../../../enum/decorator/api/controller/request/target.enum.js');
require('../../../enum/decorator/api/controller/request/transformer-type.enum.js');
require('../../../enum/decorator/api/controller/response-target.enum.js');
var dtoType_enum = require('../../../enum/decorator/api/dto-type.enum.js');
require('../../../enum/decorator/api/function/context-storage-kind.enum.js');
require('../../../enum/decorator/api/function/subscriber-transaction-expectation.enum.js');
require('../../../enum/decorator/api/function/transaction/event-status.enum.js');
require('../../../enum/decorator/api/function/transaction/failure-stage.enum.js');
require('../../../enum/decorator/api/function/transaction/mode.enum.js');
require('../../../enum/decorator/api/function/transaction/outcome.enum.js');
require('../../../enum/decorator/api/function/transaction/owner-kind.enum.js');
require('../../../enum/decorator/api/function/transaction/trace-type.enum.js');
require('../../../enum/decorator/api/function/type.enum.js');
require('../../../enum/decorator/api/on-type.enum.js');
require('../../../enum/decorator/api/property/data-type.enum.js');
require('../../../enum/decorator/api/property/date/identifier.enum.js');
require('../../../enum/decorator/api/property/date/type.enum.js');
var desribeType_enum = require('../../../enum/decorator/api/property/desribe-type.enum.js');
require('../../../enum/decorator/api/property/number-type.enum.js');
require('../../../enum/decorator/api/property/string-type.enum.js');
require('../../../enum/decorator/api/route/subscriber-authorization-expectation.enum.js');
var type_enum = require('../../../enum/decorator/api/route/type.enum.js');
var buildDecorator_utility = require('../../../utility/dto/build-decorator.utility.js');
var exception_utility = require('../../../utility/error/exception.utility.js');
var pathToRegexp = require('path-to-regexp');
const IDENTITY_PARAMETER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
class ApiControllerIdentityPlanCompiler {
static compile(controller, controllerPath, entityMetadata, method, routeConfig, readPlan) {
const identityDescriptor = Object.getOwnPropertyDescriptor(routeConfig, "identity");
if (!identityDescriptor) {
if (Reflect.has(routeConfig, "identity")) {
throw exception_utility.ErrorException("Generated identity must be an own property on the route configuration");
}
return undefined;
}
const rawIdentity = this.readTopLevelIdentity(routeConfig, identityDescriptor);
if (method !== type_enum.EApiRouteType.GET) {
throw exception_utility.ErrorException("Generated identity configuration is supported only for GET routes");
}
if (routeConfig.dto?.[dtoType_enum.EApiDtoType.PARAMETERS]) {
throw exception_utility.ErrorException("Generated identity cannot be combined with a manual PARAMETERS DTO");
}
const parameter = this.readParameter(rawIdentity);
const primaryColumn = entityMetadata.primaryKey;
const primaryMetadata = primaryColumn?.metadata?.[propertyDescribe_constant.PROPERTY_DESCRIBE_DECORATOR_API_CONSTANT.METADATA_KEY];
if (!primaryColumn || primaryColumn.relation || !primaryMetadata || primaryMetadata.type === desribeType_enum.EApiPropertyDescribeType.OBJECT || primaryMetadata.type === desribeType_enum.EApiPropertyDescribeType.RELATION) {
throw exception_utility.ErrorException("Generated identity requires a described direct scalar primary entity field");
}
const field = String(primaryColumn.name);
const inheritedParameters = this.extractPathParameters(controllerPath ?? "");
if (inheritedParameters.size > 0 && !readPlan) {
throw exception_utility.ErrorException("Generated identity on a controller path with inherited parameters requires generated read scope mappings");
}
if (inheritedParameters.has(parameter)) {
throw exception_utility.ErrorException(`Generated identity parameter "${parameter}" conflicts with an inherited controller path parameter`);
}
if (parameter !== field && inheritedParameters.has(field)) {
throw exception_utility.ErrorException(`Inherited controller path parameter "${field}" is ambiguous with generated identity parameter "${parameter}"`);
}
const currentGuard = routeConfig.security?.authentication?.guard;
if (!buildDecorator_utility.DtoBuildDecorator(method, primaryMetadata, entityMetadata, dtoType_enum.EApiDtoType.PARAMETERS, parameter, currentGuard)) {
throw exception_utility.ErrorException(`Generated identity parameter "${parameter}" maps to a primary entity field unavailable for the route PARAMETERS DTO`);
}
const normalizedPlan = { field, parameter };
const signature = node_crypto.createHash("sha256").update(JSON.stringify(normalizedPlan)).digest("hex");
const controllerName = controller.name || "AnonymousController";
return Object.freeze({
controllerName,
field,
parameter,
schemaName: `${controllerName}${entityMetadata.name ?? "UnknownResource"}GetIdentity${signature}DTO`,
signature,
});
}
static collectPathParameters(pathParts, parameters) {
for (const pathPart of pathParts) {
if (pathPart.type === "text") {
continue;
}
if (pathPart.type === "group") {
this.collectPathParameters(pathPart.tokens, parameters);
continue;
}
parameters.add(pathPart.name);
}
}
static extractPathParameters(path) {
const parameters = new Set();
let pathParts;
try {
pathParts = pathToRegexp.parse(path).tokens;
}
catch {
throw exception_utility.ErrorException(`Controller path "${path}" is not valid route syntax`);
}
this.collectPathParameters(pathParts, parameters);
return parameters;
}
static readParameter(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw exception_utility.ErrorException("Generated identity configuration must be a plain object");
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== null && prototype !== Object.prototype) {
throw exception_utility.ErrorException("Generated identity configuration must be a plain object");
}
const keys = Reflect.ownKeys(value);
if (keys.length !== 1 || keys[0] !== "parameter") {
throw exception_utility.ErrorException("Generated identity configuration must contain exactly one string key: parameter");
}
const descriptor = Object.getOwnPropertyDescriptor(value, "parameter");
if (!descriptor?.enumerable || !("value" in descriptor)) {
throw exception_utility.ErrorException("Generated identity parameter must be an enumerable data property");
}
const parameter = descriptor.value;
if (typeof parameter !== "string" || !IDENTITY_PARAMETER_PATTERN.test(parameter) || safeObjectPropertyNames_constant.UNSAFE_OBJECT_PROPERTY_NAMES_CONSTANT.has(parameter)) {
throw exception_utility.ErrorException("Generated identity parameter must be a safe simple identifier");
}
return parameter;
}
static readTopLevelIdentity(routeConfig, descriptor) {
const prototype = Object.getPrototypeOf(routeConfig);
if (prototype !== null && prototype !== Object.prototype) {
throw exception_utility.ErrorException("Generated identity route configuration must be a plain object");
}
if (Reflect.ownKeys(routeConfig).some((key) => typeof key === "symbol")) {
throw exception_utility.ErrorException("Generated identity route configuration must not contain symbol keys");
}
if (!descriptor.enumerable || !("value" in descriptor)) {
throw exception_utility.ErrorException("Generated identity must be an enumerable data property on the route configuration");
}
return descriptor.value;
}
}
exports.ApiControllerIdentityPlanCompiler = ApiControllerIdentityPlanCompiler;
//# sourceMappingURL=identity-plan-compiler.class.js.map