UNPKG

phecda-server

Version:

server framework that provide IOC/type-reuse/http&rpc-adaptor

618 lines (542 loc) 20.8 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _chunkLLF55NZPjs = require('./chunk-LLF55NZP.js'); // src/helper.ts var _picocolors = require('picocolors'); var _picocolors2 = _interopRequireDefault(_picocolors); // src/context.ts var _debug = require('debug'); var _debug2 = _interopRequireDefault(_debug); // src/pipe.ts var _phecdacore = require('phecda-core'); // src/exception/base.ts var Exception = class extends Error { static { _chunkLLF55NZPjs.__name.call(void 0, this, "Exception"); } constructor(message, metadata, status = 0, description = "Exception") { super(message), this.message = message, this.metadata = metadata, this.status = status, this.description = description; } get data() { return { message: this.message, description: this.description, status: this.status, [_chunkLLF55NZPjs.ERROR_SYMBOL]: true }; } }; // src/exception/undefine.ts var UndefinedException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "UndefinedException"); } constructor(message, metadata) { super(message, metadata, 500, "Undefined error"); } }; // src/exception/validate.ts var ValidateException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "ValidateException"); } constructor(message, metadata) { super(message, metadata, 400, "Validate exception"); } }; // src/exception/forbidden.ts var ForbiddenException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "ForbiddenException"); } constructor(message, metadata) { super(message, metadata, 403, "Forbidden resource"); } }; // src/exception/bad-request.ts var BadRequestException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "BadRequestException"); } constructor(message, metadata) { super(message, metadata, 400, "Bad Request"); } }; // src/exception/not-found.ts var NotFoundException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "NotFoundException"); } constructor(message, metadata) { super(message, metadata, 404, "Not Found"); } }; // src/exception/conflict.ts var ConflictException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "ConflictException"); } constructor(message, metadata) { super(message, metadata, 409, "Conflict"); } }; // src/exception/bad-gateway.ts var BadGatewayException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "BadGatewayException"); } constructor(message, metadata) { super(message, metadata, 502, "Bad Gatrway"); } }; // src/exception/invalid-input.ts var InvalidInputException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "InvalidInputException"); } constructor(message, metadata) { super(message, metadata, 502, "Invalid Input"); } }; // src/exception/media-type.ts var UnsupportedMediaTypeException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "UnsupportedMediaTypeException"); } constructor(message, metadata) { super(message, metadata, 415, "Unsupported Media Type"); } }; // src/exception/payload-large.ts var PayloadLargeException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "PayloadLargeException"); } constructor(message, metadata) { super(message, metadata, 413, "Payload Too Large"); } }; // src/exception/timeout.ts var TimeoutException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "TimeoutException"); } constructor(message, metadata) { super(message, metadata, 408, "Request Timeout"); } }; // src/exception/unauthorized.ts var UnauthorizedException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "UnauthorizedException"); } constructor(message, metadata) { super(message, metadata, 401, "Unauthorized"); } }; // src/exception/unavailable-service.ts var ServiceUnavailableException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "ServiceUnavailableException"); } constructor(message, metadata) { super(message, metadata, 503, "Service Unavailable"); } }; // src/exception/framework.ts var FrameworkException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "FrameworkException"); } constructor(message, metadata) { super(`[phecda-server] ${message}`, metadata, 500, "Framework Error"); } }; // src/exception/timer.ts var TimerException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "TimerException"); } constructor(message, metadata) { super(message, metadata, 0, "Timer Error"); } }; // src/exception/worker.ts var WorkerException = class extends Exception { static { _chunkLLF55NZPjs.__name.call(void 0, this, "WorkerException"); } constructor(message, metadata) { super(message, metadata, 0, "Worker Error"); } }; // src/pipe.ts var defaultPipe = /* @__PURE__ */ _chunkLLF55NZPjs.__name.call(void 0, async ({ arg, reflect, meta, index, type }, { method }) => { if (meta.const) { if (arg !== meta.const) throw new ValidateException(`param ${index + 1} must be ${meta.const}`); } if (arg === void 0) { if (meta.required === false) return arg; else throw new ValidateException(`param ${index + 1} is required`); } if ([ "params", "query" ].includes(type)) { if (reflect === Number) { arg = reflect(arg); if (isNaN(arg)) throw new ValidateException(`param ${index + 1} is not a number`); } else if (reflect === Boolean) { if (arg === "false") arg = false; else if (arg === "true") arg = true; else throw new ValidateException(`param ${index + 1} is not a boolean`); } } else { if (reflect === Number && typeof arg !== "number") throw new ValidateException(`param ${index + 1} is not a number`); if (reflect === Boolean && typeof arg !== "boolean") throw new ValidateException(`param ${index + 1} is not a boolean`); if (reflect === String && typeof arg !== "string") throw new ValidateException(`param ${index + 1} is not a string`); } if (meta.enum) { if (!Object.values(meta.enum).includes(arg)) throw new ValidateException(`param ${index + 1} is not a valid enum value`); } if (meta.oneOf) { let isCorrect = false; for (const item of meta.oneOf) { switch (item) { case String: if (typeof arg === "string") isCorrect = true; break; case Number: if (typeof arg === "number") isCorrect = true; break; case Boolean: if (typeof arg === "boolean") isCorrect = true; break; default: if (_phecdacore.isPhecda.call(void 0, item)) { const errs = await _phecdacore.validate.call(void 0, item, arg); if (!errs.length) { isCorrect = true; break; } } else if (typeof item === "function") { const ret = await item(arg); if (ret) { isCorrect = true; break; } } else { if (arg === item) { isCorrect = true; break; } } } } if (!isCorrect) throw new ValidateException(`param ${index + 1} can't pass one of these validations`); } if (meta.rules) { for (const rule of meta.rules) { const err = await rule({ value: arg, property: method, meta, model: reflect, index }); if (err.length > 0) throw new ValidateException(err[0]); } } if (_phecdacore.isPhecda.call(void 0, reflect)) { const errs = await _phecdacore.validate.call(void 0, reflect, arg); if (errs.length) throw new ValidateException(errs[0]); } return arg; }, "defaultPipe"); // src/filter.ts var defaultFilter = /* @__PURE__ */ _chunkLLF55NZPjs.__name.call(void 0, (e) => { if (!(e instanceof Exception)) { _chunkLLF55NZPjs.log.call(void 0, e.message, "error"); if (_chunkLLF55NZPjs.LOG_LEVEL <= 0) console.error(e.stack); e = new UndefinedException(e.message || e); } else { _chunkLLF55NZPjs.log.call(void 0, `[${e.constructor.name}] ${e.message}`, "error"); if (_chunkLLF55NZPjs.LOG_LEVEL <= 0) console.error(e.stack); } return e.data; }, "defaultFilter"); // src/context.ts var debug = _debug2.default.call(void 0, "phecda-server(Context)"); var Context = (_class = class _Context { static { _chunkLLF55NZPjs.__name.call(void 0, this, "Context"); } static __initStatic() {this.filterRecord = { default: defaultFilter }} static __initStatic2() {this.pipeRecord = { default: defaultPipe }} static __initStatic3() {this.guardRecord = {}} static __initStatic4() {this.addonRecord = {}} // protected canGetCtx = true constructor(data) { this.data = data; if (_chunkLLF55NZPjs.IS_DEV) data._context = this; this.ctx = new Proxy(data, { get(target, p) { if (!(p in target)) _chunkLLF55NZPjs.log.call(void 0, `attribute "${p}" does not exist on ctx, which might be due to a missing AOP role (such as a guard).`, "warn", data.tag); return target[p]; }, set(target, p, newValue) { target[p] = newValue; return true; } }); } static getAop(meta, opts) { const { globalGuards = [], globalFilter = "default", globalPipe = "default" } = opts; const { data: { guards, filter, params, tag, method } } = meta; const resolved = { guards: [ ...globalGuards, ...guards ], pipe: params.map((item) => item.pipe || globalPipe), filter: filter || globalFilter }; if (process.env.DEBUG) { const { guards: guards2, pipe, filter: filter2 } = resolved; debug(`method "${tag}-${method}" aop: ${_picocolors2.default.magenta(`Guard ${guards2.join("->")}[${guards2.filter((g) => g in this.guardRecord).join("->")}]`)} ${_picocolors2.default.blue(`Pipe ${pipe.join("-")}[${pipe.map((p) => p in this.pipeRecord ? p : "default").join("-")}]`)} ${_picocolors2.default.red(`Filter ${filter2}[${filter2 || "default"}]`)}`); } return { guards: this.getGuards(resolved.guards), pipe: this.getPipe(resolved.pipe), filter: this.getFilter(resolved.filter) }; } async run({ guards, filter, pipe }, successCb, failCb) { const { meta, moduleMap } = this.data; const { paramsType, data: { ctxs, tag, params, method } } = meta; try { let res; const nextHandler = /* @__PURE__ */ _chunkLLF55NZPjs.__name.call(void 0, (index) => { return async () => { if (index === guards.length) { const instance = moduleMap.get(tag); if (ctxs) { ctxs.forEach((ctx) => instance[ctx] = this.ctx); } const args = await Promise.all(params.map((item, i) => pipe[i]({ arg: resolveDep(this.data[item.type], item.key), reflect: paramsType[item.index], ...item }, this.ctx))); res = await instance[method](...args); } else { let nextPromise; async function next() { return nextPromise = nextHandler(index + 1)().then((ret2) => { if (ret2 !== void 0) { debug(`The ${index + 1}th guard on "${tag}-${method}" rewrite the response value.`); res = ret2; } return res; }); } _chunkLLF55NZPjs.__name.call(void 0, next, "next"); const ret = await guards[index](this.ctx, next); if (ret !== void 0) { res = ret; } else { if (!nextPromise) await next(); else await nextPromise; } } }; }, "nextHandler"); await nextHandler(0)(); return successCb(res); } catch (e) { const err = await filter(e, this.ctx); return failCb(err); } } static getPipe(pipe) { return pipe.map((pipe2) => { return _Context.pipeRecord[pipe2] || _Context.pipeRecord.default; }); } static getFilter(filter = "default") { return _Context.filterRecord[filter] || _Context.filterRecord.default; } static getGuards(guards) { const ret = []; for (const guard of new Set(guards)) { if (guard in _Context.guardRecord) ret.push(_Context.guardRecord[guard]); } return ret.sort((a, b) => b.priority - a.priority).map((item) => item.value); } static applyAddons(addons, router, framework) { const ret = []; for (const a of new Set(addons)) { if (a in _Context.addonRecord) ret.push(_Context.addonRecord[a]); } ret.sort((a, b) => b.priority - a.priority).forEach((item) => item.value(router, framework)); } }, _class.__initStatic(), _class.__initStatic2(), _class.__initStatic3(), _class.__initStatic4(), _class); function addPipe(key, pipe) { if (Context.pipeRecord[key] && Context.pipeRecord[key] !== pipe) debug(`overwrite Pipe "${String(key)}"`, "warn"); Context.pipeRecord[key] = pipe; } _chunkLLF55NZPjs.__name.call(void 0, addPipe, "addPipe"); function addFilter(key, filter) { if (Context.filterRecord[key] && Context.filterRecord[key] !== filter) debug(`overwrite Filter "${String(key)}"`, "warn"); Context.filterRecord[key] = filter; } _chunkLLF55NZPjs.__name.call(void 0, addFilter, "addFilter"); function addGuard(key, guard, priority = 0) { if (Context.guardRecord[key] && Context.guardRecord[key].value !== guard) debug(`overwrite Guard "${String(key)}"`, "warn"); Context.guardRecord[key] = { value: guard, priority }; } _chunkLLF55NZPjs.__name.call(void 0, addGuard, "addGuard"); function addAddon(key, addon, priority = 0) { if (Context.addonRecord[key] && Context.addonRecord[key].value !== addon) debug(`overwrite Addon "${String(key)}"`, "warn"); Context.addonRecord[key] = { value: addon, priority }; } _chunkLLF55NZPjs.__name.call(void 0, addAddon, "addAddon"); // src/http/helper.ts function resolveDep(ret, key) { if (key) return _optionalChain([ret, 'optionalAccess', _ => _[key]]); return ret; } _chunkLLF55NZPjs.__name.call(void 0, resolveDep, "resolveDep"); // src/decorators/helper.ts function shallowClone(obj) { return { ...obj }; } _chunkLLF55NZPjs.__name.call(void 0, shallowClone, "shallowClone"); function mergeObject(...args) { return Object.assign({}, ...args); } _chunkLLF55NZPjs.__name.call(void 0, mergeObject, "mergeObject"); // src/helper.ts function createControllerMetaMap(meta, filter) { const metaMap = /* @__PURE__ */ new Map(); function handleMeta() { metaMap.clear(); for (const item of meta) { const { tag, method } = item.data; if (!filter(item)) continue; if (metaMap.has(tag)) metaMap.get(tag)[method] = item; else metaMap.set(tag, { [method]: item }); } } _chunkLLF55NZPjs.__name.call(void 0, handleMeta, "handleMeta"); handleMeta(); _chunkLLF55NZPjs.HMR.call(void 0, handleMeta); return metaMap; } _chunkLLF55NZPjs.__name.call(void 0, createControllerMetaMap, "createControllerMetaMap"); function detectAopDep(meta, { guards, addons } = {}, controller = "http") { if (_chunkLLF55NZPjs.IS_PURE) return; const addonSet = /* @__PURE__ */ new Set(); const guardSet = /* @__PURE__ */ new Set(); const pipeSet = /* @__PURE__ */ new Set(); const filterSet = /* @__PURE__ */ new Set(); const warningSet = /* @__PURE__ */ new Set(); function handleMeta() { addonSet.clear(); guardSet.clear(); pipeSet.clear(); filterSet.clear(); warningSet.clear(); _optionalChain([addons, 'optionalAccess', _2 => _2.forEach, 'call', _3 => _3((item) => { addonSet.add(item); })]); _optionalChain([guards, 'optionalAccess', _4 => _4.forEach, 'call', _5 => _5((item) => { guardSet.add(item); })]); meta.forEach(({ data }) => { if (!data.controller) return; if (typeof data.tag !== "string") warningSet.add(`Tag of controller "${data.name}" should be a string`); if (data.controller !== controller) { if (data[controller]) warningSet.add(`Should use ${controller} controller to decorate class "${data.name}"`); return; } if (data.filter) filterSet.add(data.filter); data.guards.forEach((i) => guardSet.add(i)); data.addons.forEach((i) => addonSet.add(i)); data.params.forEach((i) => { if (i.pipe) pipeSet.add(i.pipe); }); }); const missAddons = [ ...addonSet ].filter((i) => !Context.addonRecord[i]); const missGuards = [ ...guardSet ].filter((i) => !Context.guardRecord[i]); const missPipes = [ ...pipeSet ].filter((i) => !Context.pipeRecord[i]); const missFilters = [ ...filterSet ].filter((i) => !Context.filterRecord[i]); function exit() { if (_chunkLLF55NZPjs.IS_STRICT) { _chunkLLF55NZPjs.log.call(void 0, "Does not meet strict mode requirements", "error"); process.exit(1); } } _chunkLLF55NZPjs.__name.call(void 0, exit, "exit"); if (missAddons.length) { _chunkLLF55NZPjs.log.call(void 0, `${_picocolors2.default.white(`Addon [${missAddons.join(",")}]`)} doesn't exist`, "warn"); exit(); } if (missGuards.length) { _chunkLLF55NZPjs.log.call(void 0, `${_picocolors2.default.magenta(`Guard [${missGuards.join(",")}]`)} doesn't exist`, "warn"); exit(); } if (missPipes.length) { _chunkLLF55NZPjs.log.call(void 0, `${_picocolors2.default.blue(`Pipe [${missPipes.join(",")}]`)} doesn't exist`, "warn"); exit(); } if (missFilters.length) { _chunkLLF55NZPjs.log.call(void 0, `${_picocolors2.default.red(`Filter [${missFilters.join(",")}]`)} doesn't exist`, "warn"); exit(); } warningSet.forEach((warn) => _chunkLLF55NZPjs.log.call(void 0, warn, "warn")); if (warningSet.size) exit(); } _chunkLLF55NZPjs.__name.call(void 0, handleMeta, "handleMeta"); handleMeta(); _chunkLLF55NZPjs.HMR.call(void 0, handleMeta); return { addonSet, guardSet, pipeSet, filterSet }; } _chunkLLF55NZPjs.__name.call(void 0, detectAopDep, "detectAopDep"); function joinUrl(base, ...paths) { const joinedPath = [ base, ...paths ].filter((p) => p).map((path) => path.replace(/(^\/)/g, "")).join("/"); return `/${joinedPath}`; } _chunkLLF55NZPjs.__name.call(void 0, joinUrl, "joinUrl"); exports.Exception = Exception; exports.UndefinedException = UndefinedException; exports.ValidateException = ValidateException; exports.ForbiddenException = ForbiddenException; exports.BadRequestException = BadRequestException; exports.NotFoundException = NotFoundException; exports.ConflictException = ConflictException; exports.BadGatewayException = BadGatewayException; exports.InvalidInputException = InvalidInputException; exports.UnsupportedMediaTypeException = UnsupportedMediaTypeException; exports.PayloadLargeException = PayloadLargeException; exports.TimeoutException = TimeoutException; exports.UnauthorizedException = UnauthorizedException; exports.ServiceUnavailableException = ServiceUnavailableException; exports.FrameworkException = FrameworkException; exports.TimerException = TimerException; exports.WorkerException = WorkerException; exports.defaultPipe = defaultPipe; exports.defaultFilter = defaultFilter; exports.Context = Context; exports.addPipe = addPipe; exports.addFilter = addFilter; exports.addGuard = addGuard; exports.addAddon = addAddon; exports.resolveDep = resolveDep; exports.shallowClone = shallowClone; exports.mergeObject = mergeObject; exports.createControllerMetaMap = createControllerMetaMap; exports.detectAopDep = detectAopDep; exports.joinUrl = joinUrl;