UNPKG

@sleekify/sleekify

Version:

A TypeScript decorator driven approach for developing web applications.

349 lines (348 loc) 14.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Annotation = void 0; const node_fs_1 = __importDefault(require("node:fs")); const lodash_1 = __importDefault(require("lodash")); const node_path_1 = __importDefault(require("node:path")); const PathUtil_1 = require("../utils/PathUtil"); const StringUtil_1 = require("../utils/StringUtil"); ; ; const EMPTY = {}; /** * Gets or sets annotations on a class or class property. */ class Annotation { constructor() { throw new Error('Annotation cannot be instantiated'); } /** * Indicates whether the annotation exists on the class or property. * @param target The class * @param propertyKey The property name * @param decorator The decorator associated with the annotation * @returns The annotation's value. */ static exists(target, propertyKey, decorator) { const settings = decorator.$annotations; if (settings === undefined) { return false; } const { key: decoratorKey, isInherited } = settings; let valueList; if (undefined === propertyKey) { valueList = Annotation.getValueListForClass(target, decoratorKey, isInherited); } else { valueList = Annotation.getValueListForProperty(target, propertyKey, decoratorKey, isInherited); } return valueList.length > 0; } /** * Gets the annotation's value. * @param target The class * @param propertyKey The property name * @param decorator The decorator associated with the annotation * @returns The annotation's value. */ static get(target, propertyKey, decorator) { const settings = decorator.$annotations; if (settings === undefined) { return; } const { key: decoratorKey, isAdditive, isInherited } = settings; let valueList; if (undefined === propertyKey) { valueList = Annotation.getValueListForClass(target, decoratorKey, isInherited); } else { valueList = Annotation.getValueListForProperty(target, propertyKey, decoratorKey, isInherited); } if (valueList.length === 0 || valueList.every(((v) => v === EMPTY))) { /* no values */ return; } if (!lodash_1.default.isObject(valueList[0]) || lodash_1.default.isArray(valueList[0])) { /* primitive or array value */ return valueList[0]; } /* build an object which inherits from parent decorator values */ const objectValue = {}; for (const value of valueList) { if (lodash_1.default.isObject(value)) { lodash_1.default.defaultsDeep(objectValue, value); } if (!isAdditive) { break; } } return objectValue; } /** * Finds classes annotated with the decoration by searching files recursively * under the provided relative path. The classes must be exported as a named * exports since default exports aren't supported. * * @param relativePath The relative file path to search * @param decorator The decorator applied to the class */ static async getClassesAnnotatedWith(relativePath, decorator) { const callerFilename = PathUtil_1.PathUtil.getCallerFilename(); const callerDirectory = PathUtil_1.PathUtil.filenameToDirectory(callerFilename); const currentDir = process.cwd(); const tsConfigPath = node_path_1.default.join(currentDir, 'tsconfig.json'); let isTypeScript = false; let hasTypesScriptOutDir = false; if (/[.]ts$/.test(callerFilename)) { isTypeScript = true; } if (isTypeScript && node_fs_1.default.existsSync(tsConfigPath)) { const contents = node_fs_1.default.readFileSync(tsConfigPath); const tsConfig = JSON.parse(contents.toString()); const outDir = tsConfig.compilerOptions?.outDir; if (outDir != null && outDir.length > 0) { hasTypesScriptOutDir = true; } } let updatedRelativePath = relativePath.replace(/\//g, node_path_1.default.sep); let updatedDirectory = callerDirectory; const parentDirectoryRegex = new RegExp(`[.][.]\\${node_path_1.default.sep}`); while (parentDirectoryRegex.test(updatedRelativePath)) { updatedRelativePath = updatedRelativePath.substring(3); updatedDirectory = updatedDirectory.substring(0, updatedDirectory.lastIndexOf(node_path_1.default.sep)); } updatedRelativePath = updatedRelativePath.replace(`.${node_path_1.default.sep}`, ''); const globPath = `${updatedDirectory}${updatedRelativePath !== '' ? `${node_path_1.default.sep}${updatedRelativePath}` : ''}${node_path_1.default.sep}**${node_path_1.default.sep}`.replace(/\\/g, '/'); const myDirectory = __dirname; const commonBasePath = PathUtil_1.PathUtil.getCommonBasePath(`${currentDir}${node_path_1.default.sep}`, `${myDirectory}${node_path_1.default.sep}`); const mySegmentCount = myDirectory.replace(commonBasePath, '').split(node_path_1.default.sep).length; const modulePaths = await PathUtil_1.PathUtil.getModulePaths(globPath, { isTypeScript, hasTypesScriptOutDir }); const modulePathPrefixSuffix = `${currentDir}${node_path_1.default.sep}`.replace(commonBasePath, ''); const modulePathPrefix = `${`..${node_path_1.default.sep}`.repeat(mySegmentCount)}${modulePathPrefixSuffix}`; const moduleBasePath = `${currentDir}${node_path_1.default.sep}`; return await Annotation.importClasses(modulePaths, modulePathPrefix, moduleBasePath, decorator); } /** * Every annotation must be registered with a unique key and its annotation options. * * @param decorator The decorator being used as an annotation * @param key The decorator's unique key * @param options The decorator's options (optional) */ static register(decorator, key, options) { const annotatedDecorator = decorator; if (annotatedDecorator.$annotations === undefined) { annotatedDecorator.$annotations = { key, isInherited: options?.isInherited ?? true, isAdditive: options?.isAdditive ?? true }; } } /** * Sets the annotation's value. * @param target The class * @param propertyKey The property name * @param decorator The decorator being used as an annotation * @param value The annotation's value */ static set(target, propertyKey, decorator, value) { const annotatedDecorator = decorator; if (annotatedDecorator.$annotations === undefined) { throw new Error('The annotation has not been registered'); } const decoratorKey = annotatedDecorator.$annotations.key; const annotationMap = Annotation.getAnnotationMap(target, propertyKey); if (undefined === annotationMap) { return; } annotationMap[decoratorKey] = value ?? EMPTY; } static getAnnotationMap(target, propertyKey) { if (undefined === propertyKey) { const classReference = target; const classKey = this.getClassKey(classReference); /* class annotation */ if (undefined === classReference.$annotations) { classReference.$annotations = {}; } if (undefined === classReference.$annotations[classKey]) { classReference.$annotations[classKey] = {}; } return classReference.$annotations[classKey]; } const descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); const targetValue = target[propertyKey]; if (undefined !== descriptor && lodash_1.default.isFunction(targetValue)) { const classReference = target.constructor.name === 'Function' ? target : target.constructor; const classKey = this.getClassKey(classReference); /* property annotation */ if (undefined === classReference.$annotations) { classReference.$annotations = {}; } if (undefined === classReference.$annotations[classKey]) { classReference.$annotations[classKey] = {}; } if (undefined === classReference.$annotations[classKey][propertyKey]) { classReference.$annotations[classKey][propertyKey] = {}; } return classReference.$annotations[classKey][propertyKey]; } return undefined; } static getClassKey(target) { return ClassKeyCache.get(target); } static getValueForClass(target, decoratorKey) { if (undefined === target.$annotations) { return undefined; } const classKey = this.getClassKey(target); if (undefined === target.$annotations[classKey]) { return undefined; } return target.$annotations[classKey][decoratorKey]; } static getValueListForClass(target, decoratorKey, inherited) { const valueList = []; for (let currentTarget = target; currentTarget !== undefined && currentTarget?.constructor?.name !== 'Object'; currentTarget = Object.getPrototypeOf(currentTarget)) { const value = Annotation.getValueForClass(currentTarget, decoratorKey); if (value != null) { valueList.push(value); } if (!inherited) { break; } } return valueList; } static getValueForProperty(target, propertyKey, decoratorKey) { if (undefined === target.$annotations) { return undefined; } const classKey = this.getClassKey(target); if (undefined === target.$annotations[classKey]) { return undefined; } if (undefined === target.$annotations[classKey][propertyKey]) { return undefined; } return target.$annotations[classKey][propertyKey][decoratorKey]; } static getValueListForProperty(target, propertyKey, decoratorKey, inherited) { const valueList = []; let isProperty = false; const staticDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey); const staticTargetValue = (target)[propertyKey]; if (undefined !== staticDescriptor && lodash_1.default.isFunction(staticTargetValue)) { /* static property annotation */ isProperty = true; } else { const instanceTargetValue = target.prototype?.[propertyKey]; if (lodash_1.default.isFunction(instanceTargetValue)) { /* property annotation */ isProperty = true; } } if (isProperty) { for (let currentTarget = target; currentTarget !== undefined && currentTarget?.constructor?.name !== 'Object'; currentTarget = Object.getPrototypeOf(currentTarget)) { const value = Annotation.getValueForProperty(currentTarget, propertyKey, decoratorKey); if (value != null) { valueList.push(value); } if (!inherited) { break; } } } return valueList; } static async importClasses(modulePaths, modulePathPrefix, moduleBasePath, decorator) { const classArray = []; for (const modulePath of modulePaths) { const relativeModulePath = `${modulePathPrefix}${modulePath.replace(moduleBasePath, '')}`.replace(/\\/g, '/'); /* ignore types and tests */ if (/[.]d[.]ts$/.test(relativeModulePath) || /[.]spec[.][jt]s$/.test(relativeModulePath)) { continue; } const module = await Promise.resolve(`${relativeModulePath}`).then(s => __importStar(require(s))); if (lodash_1.default.isObject(module)) { for (const exportName in module) { const exportValue = module[exportName]; if (lodash_1.default.isObject(exportValue)) { if (Annotation.exists(exportValue, undefined, decorator)) { classArray.push(exportValue); } } } } } return classArray; } } exports.Annotation = Annotation; /** * Class keys are hash codes of the cache definition. Cache them to improve performance. */ class ClassKeyCache { static classNameMap = {}; /** * Get the key for the given class. * * @param target The class * @returns The class key or zero. */ static get(target) { const constructorName = target?.prototype?.constructor?.name; let entry = { reference: target, key: 0 }; if (constructorName != null) { let entryList = ClassKeyCache.classNameMap[constructorName]; if (entryList === undefined) { entryList = []; ClassKeyCache.classNameMap[constructorName] = entryList; } entry = entryList.find((v) => v.reference === target); if (entry === undefined) { entry = { reference: target, key: StringUtil_1.StringUtil.getHashCode(target.toString()) }; entryList.push(entry); } } return entry.key; } }