@plumier/core
Version:
Delightful Node.js Rest Framework
271 lines (270 loc) • 10.5 kB
JavaScript
;
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;