UNPKG

@plumier/core

Version:

Delightful Node.js Rest Framework

271 lines (270 loc) • 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findClassRecursive = exports.appendRoute = exports.globAsync = exports.analyzeModel = exports.printTable = exports.memoize = exports.findFilesRecursive = exports.entityHelper = exports.isCustomClass = exports.hasKeyOf = exports.getChildValue = exports.toBoolean = exports.ellipsis = void 0; const tslib_1 = require("tslib"); const fs_1 = require("fs"); const glob_1 = tslib_1.__importDefault(require("glob")); const reflect_1 = tslib_1.__importStar(require("@plumier/reflect")); const util_1 = require("util"); const types_1 = require("./types"); const path_1 = require("path"); const lstatAsync = (0, util_1.promisify)(fs_1.lstat); const existsAsync = (0, util_1.promisify)(fs_1.exists); String.prototype.format = function (...args) { return this.replace(/{(\d+)}/g, (m, i) => args[i]); }; Array.prototype.flatten = function () { return this.reduce((a, b) => a.concat(b), []); }; function ellipsis(str, length) { if (str.length > length) { const leftPart = str.substring(0, length - 9); const rightPart = str.substring(str.length - 6); return `${leftPart}...${rightPart}`; } else return str; } exports.ellipsis = ellipsis; function getChildValue(object, path) { return path .split(/[\.\[\]\'\"]/) .filter(p => p) .reduce((o, p) => o[p], object); } exports.getChildValue = getChildValue; function hasKeyOf(opt, key) { return !!opt[key]; } exports.hasKeyOf = hasKeyOf; function toBoolean(val) { var _a; const list = { on: true, true: true, "1": true, yes: true, off: false, false: false, "0": false, no: false }; return (_a = list[val.toLowerCase()]) !== null && _a !== void 0 ? _a : false; } exports.toBoolean = toBoolean; function isCustomClass(type) { switch (type && type[0] || type) { case undefined: case Boolean: case String: case Array: case Number: case Object: case Date: return false; default: return true; } } exports.isCustomClass = isCustomClass; function memoize(fn, getKey) { const cache = new Map(); return (0, reflect_1.useCache)(cache, fn, getKey); } exports.memoize = memoize; // --------------------------------------------------------------------- // // ---------------------------- FILE SYSTEM ---------------------------- // // --------------------------------------------------------------------- // function removeExtension(x) { return x.replace(/\.[^/.]+$/, ""); } function globAsync(path, opts) { return new Promise((resolve) => { (0, glob_1.default)(path, Object.assign({}, opts), (e, match) => resolve(match)); }); } exports.globAsync = globAsync; async function traverseDirectory(path) { const dirs = await globAsync(path, { nodir: true }); const files = dirs.map(x => removeExtension(x)); return Array.from(new Set(files)); } async function findFilesRecursive(path) { // if file / directory provided if (await existsAsync(path)) { if ((await lstatAsync(path)).isDirectory()) { return traverseDirectory(`${path}/**/*.{ts,js}`); } else return [removeExtension(path)]; } // else check if glob provided return traverseDirectory(path); } exports.findFilesRecursive = findFilesRecursive; function appendRoute(...args) { return "/" + args .filter(x => !!x) .map(x => x.toLowerCase()) .map(x => x.startsWith("/") ? x.slice(1) : x) .map(x => x.endsWith("/") ? x.slice(0, -1) : x) .filter(x => !!x) .join("/"); } exports.appendRoute = appendRoute; function getRoot(rootPath, path) { // directoryAsPath should not working with glob if (rootPath.indexOf("*") >= 0) return; const part = path.slice(rootPath.length).split("/").filter(x => !!x) .slice(0, -1); return (part.length === 0) ? undefined : appendRoute(...part); } async function findClassRecursive(path, option) { var _a; const opt = Object.assign({ rootDir: "", directoryAsPath: false }, option); if (Array.isArray(path)) { const result = []; for (const p of path) { result.push(...await findClassRecursive(p, opt)); } return result; } if (typeof path === "string") { const absPath = (0, path_1.isAbsolute)(path) ? path : (0, path_1.join)(opt.rootDir, path); //read all files and get module reflection const files = await findFilesRecursive(absPath); const result = []; for (const file of files) { const root = !!opt.directoryAsPath ? ((_a = getRoot(absPath, file)) !== null && _a !== void 0 ? _a : "") : ""; for (const member of (0, reflect_1.default)(file).members) { if (member.kind === "Class") result.push({ root, type: member.type }); } } return result; } else return [{ root: "", type: path }]; } exports.findClassRecursive = findClassRecursive; function printTable(meta, data, option) { const getText = (col, row) => { var _a; if (typeof col.property === "string") return ((_a = row[col.property]) !== null && _a !== void 0 ? _a : "") + ""; else return col.property(row); }; const metaData = meta.filter((x) => !!x).map(x => typeof x === "string" ? { property: x } : x) .map(x => { const lengths = data.map(row => getText(x, row).length); const length = Math.max(...lengths); return Object.assign(Object.assign({}, x), { margin: x.align || "left", length }); }); const opt = Object.assign({ onPrintRow: x => x }, option); for (const [i, row] of data.entries()) { // row number let text = `${(i + 1).toString().padStart(data.length.toString().length)}. `; for (const [idx, col] of metaData.entries()) { const exceptLast = idx < metaData.length - 1; const colText = getText(col, row); // margin if (col.margin === "right") text += colText.padStart(col.length); else if (exceptLast) text += colText.padEnd(col.length); else text += colText; //padding if (exceptLast) text += " "; } console.log(opt.onPrintRow(text, row)); } } exports.printTable = printTable; function analyzeModel(type, ctx = { path: [], parentPath: [] }) { const parentType = ctx.parentPath[ctx.parentPath.length - 1]; const propName = ctx.path[ctx.path.length - 1]; const location = `${parentType === null || parentType === void 0 ? void 0 : parentType.name}.${propName}`; if (Array.isArray(type)) { if (type[0] === Object) return [{ location, issue: "ArrayTypeMissing" }]; return analyzeModel(type[0], ctx); } if (isCustomClass(type)) { // CIRCULAR: check if type already in path, skip immediately if (ctx.parentPath.some(x => x === type)) return []; const meta = (0, reflect_1.default)(type); if (meta.properties.length === 0) return [{ location: type.name, issue: "NoProperties" }]; const result = []; for (const prop of meta.properties) { const path = ctx.path.concat(prop.name); const typePath = ctx.parentPath.concat(type); const msgs = analyzeModel(prop.type, Object.assign(Object.assign({}, ctx), { path, parentPath: typePath })); result.push(...msgs); } return result; } if (type === Object) return [{ location, issue: "TypeMissing" }]; return []; } exports.analyzeModel = analyzeModel; var entityHelper; (function (entityHelper) { function getIdProp(entity) { const meta = (0, reflect_1.default)(entity); for (const prop of meta.properties) { const decorator = prop.decorators.find((x) => x.kind === "plumier-meta:entity-id"); if (decorator) return prop; } } entityHelper.getIdProp = getIdProp; function getIdType(entity) { const prop = getIdProp(entity); return prop === null || prop === void 0 ? void 0 : prop.type; } entityHelper.getIdType = getIdType; function getRelationInfo([entity, relation]) { const meta = (0, reflect_1.default)(entity); const prop = meta.properties.find(x => x.name === relation); if (!prop) throw new Error(`${entity.name} doesn't have property named ${relation}`); if (prop.type === Array && !prop.type[0]) throw new Error(types_1.errorMessage.UnableToGetMemberDataType.format(entity.name, prop.name)); const type = Array.isArray(prop.type) ? "OneToMany" : "ManyToOne"; if (type === "OneToMany") { const relDecorator = prop.decorators.find((x) => x.kind === "plumier-meta:relation"); if (!relDecorator) throw new Error(`${entity.name}.${relation} is not a valid relation, make sure its decorated with @entity.relation() decorator`); const child = prop.type[0]; return { type, parent: entity, child, parentProperty: relation, childProperty: relDecorator.inverseProperty, }; } else { const parent = prop.type; if (!parent) throw new Error(types_1.errorMessage.UnableToGetMemberDataType.format(entity.name, prop.name)); const parentMeta = (0, reflect_1.default)(parent); let parentProperty; for (const prop of parentMeta.properties) { const relDecorator = prop.decorators.find((x) => x.kind === "plumier-meta:relation"); if (!relDecorator) continue; if (relDecorator.inverseProperty === relation) { parentProperty = prop.name; break; } } return { type, parent, child: entity, childProperty: relation, parentProperty }; } } entityHelper.getRelationInfo = getRelationInfo; })(entityHelper || (entityHelper = {})); exports.entityHelper = entityHelper;