UNPKG

@kitmi/jacaranda

Version:

JavaScript application framework

335 lines (334 loc) 16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return _default; } }); const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path")); const _utils = require("@kitmi/utils"); const _types = require("@kitmi/types"); const _defaultServerOpts = require("./defaultServerOpts"); const _loadModuleFrom_ = require("./helpers/loadModuleFrom_"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const Routable = (T)=>{ class _class extends T { async start_() { /** * Middleware factory registry. * @member {object} */ this._middlewareFactories = {}; await super.start_(); if (this.options.logMiddlewareRegistry && (this.options.logLevel === 'verbose' || this.options.logLevel === 'debug')) { this.log('verbose', 'Registered middlewares:', { middlewares: this.registry.middlewares ? Object.keys(this.registry.middlewares) : [] }); } return this; } async stop_() { delete this._middlewareFactories; return super.stop_(); } /** * Get the app module by route or alias. * @param {String|WebModule} fromApp * @returns {WebModule} */ getOtherApp(fromApp) { let _app; if (typeof fromApp === 'object' && fromApp.isModule) { return fromApp; } if (typeof fromApp !== 'string') { throw new _types.InvalidArgument('"fromApp" must be a string or WebModule'); } if (fromApp.startsWith('/')) { _app = this.server.getAppByRoute(fromApp); } else { _app = this.server.getAppByAlias(fromApp); } return _app; } /** * Register the factory method of a named middleware. * @param {string} name - The name of the middleware * @param {function} factoryMethod - The factory method of a middleware */ registerMiddlewareFactory(name, factoryMethod) { if (typeof factoryMethod !== 'function') { if (factoryMethod.__esModule && typeof factoryMethod.default === 'function') { factoryMethod = factoryMethod.default; } else { throw new _types.InvalidArgument('Invalid middleware factory: ' + name); } } if (name in this._middlewareFactories) { throw new _types.ApplicationError('Middleware "' + name + '" already registered!'); } this._middlewareFactories[name] = factoryMethod; this.log('verbose', `Registered named middleware [${name}].`); } /** * Get the factory method of a middleware from module hierarchy. * @param name * @returns {Promise.<function>} */ async getMiddlewareFactory_(name) { const factory = this._middlewareFactories[name]; if (factory != null) { return factory; } const registryEntry = await (0, _loadModuleFrom_.tryLoadFrom_)(this, 'Middleware', { registry: { name, path: 'middlewares' }, project: { name, path: _nodepath.default.join(this.sourcePath, 'middlewares') } }, true); if (registryEntry != null) { this._middlewareFactories[name] = registryEntry; return registryEntry; } if (this.server && !this.isServer) { return this.server.getMiddlewareFactory_(name); } throw new _types.ApplicationError(`Middleware "${name}" not found in middleware registry.`); } /** * Use middlewares by creating middleware instances (asynchronously) from factory methods and attach them to a router. * @param {Router} router * @param {*} middlewares - Can be an array of middleware entries or a key-value list of registerred middlewares * @returns {Routable} */ async useMiddlewares_(router, middlewares) { let middlewareFactory, middleware; let middlewareFunctions = []; if ((0, _utils.isPlainObject)(middlewares)) { await (0, _utils.eachAsync_)(middlewares, async (options, name)=>{ middlewareFactory = await this.getMiddlewareFactory_(name); middleware = await middlewareFactory(options, this); middlewareFunctions.push({ name, middleware }); }); } else { middlewares = _utils._.castArray(middlewares); await (0, _utils.eachAsync_)(middlewares, async (middlewareEntry)=>{ let type = typeof middlewareEntry; if (type === 'string') { // [ 'namedMiddleware' ] middlewareFactory = await this.getMiddlewareFactory_(middlewareEntry); middleware = await middlewareFactory(undefined, this); middlewareFunctions.push({ name: middlewareEntry, middleware }); } else if (type === 'function') { middlewareFunctions.push({ name: middlewareEntry.name || 'unamed middleware', middleware: middlewareEntry }); } else if (Array.isArray(middlewareEntry)) { // [ [ 'namedMiddleware', config ] ] if (middlewareEntry.length === 0) { throw new _types.InvalidConfiguration('Empty array found as middleware entry!', this, 'middlewares'); } middlewareFactory = await this.getMiddlewareFactory_(middlewareEntry[0]); middleware = await middlewareFactory(middlewareEntry.length > 1 ? middlewareEntry[1] : null, this); middlewareFunctions.push({ name: middlewareEntry[0], middleware }); } else { if (!(0, _utils.isPlainObject)(middlewareEntry) || !('name' in middlewareEntry)) { throw new _types.InvalidConfiguration('Invalid middleware entry!', this, 'middlewares'); } middlewareFactory = await this.getMiddlewareFactory_(middlewareEntry.name); middleware = await middlewareFactory(middlewareEntry.options, this); middlewareFunctions.push({ name: middlewareEntry.name, middleware }); } }); } middlewareFunctions.forEach(({ name, middleware })=>{ if (Array.isArray(middleware)) { middleware.forEach((m)=>this.useMiddleware(router, m, name)); } else { this.useMiddleware(router, middleware, name); } }); return this; } /** * Add a route to a router, skipped while the server running in deaf mode. * @param router * @param method * @param route * @param actions */ async addRoute_(router, method, route, actions) { let handlers = [], middlewareFactory; if ((0, _utils.isPlainObject)(actions)) { await (0, _utils.eachAsync_)(actions, async (options, name)=>{ middlewareFactory = await this.getMiddlewareFactory_(name); handlers.push(this._wrapMiddlewareTracer(await middlewareFactory(options, this), name)); }); } else { actions = _utils._.castArray(actions); let lastIndex = actions.length - 1; await (0, _utils.eachAsync_)(actions, async (action, i)=>{ let type = typeof action; if (i === lastIndex) { // last middleware may be an action if (type === 'string' && action.lastIndexOf('.') > 0) { action = { name: 'action', options: action }; type = 'object'; } } if (type === 'string') { // [ 'namedMiddleware' ] middlewareFactory = await this.getMiddlewareFactory_(action); let middleware = await middlewareFactory(null, this); //in case it's register by the middlewareFactory feature if (Array.isArray(middleware)) { middleware.forEach((middlewareItem, i)=>handlers.push(this._wrapMiddlewareTracer(middlewareItem, `${action}-${i}` + (middleware.name ? '-' + middleware.name : '')))); } else { handlers.push(this._wrapMiddlewareTracer(middleware, action)); } } else if (type === 'function') { handlers.push(this._wrapMiddlewareTracer(action)); } else if (Array.isArray(action)) { if (action.length === 0 || action.length > 2) { throw new _types.InvalidConfiguration('Invalid middleware entry!', this, 'middlewares'); } middlewareFactory = await this.getMiddlewareFactory_(action[0]); handlers.push(this._wrapMiddlewareTracer(await middlewareFactory(action.length > 1 ? action[1] : undefined, this))); } else { if (typeof action !== 'object' || !('name' in action)) { throw new _types.InvalidConfiguration('Invalid middleware entry!', this, 'middlewares'); } middlewareFactory = await this.getMiddlewareFactory_(action.name); handlers.push(this._wrapMiddlewareTracer(await middlewareFactory(action.options, this), action.name)); } }); } router[method](route, ...handlers); let endpoint = _utils.text.dropIfEndsWith(router.opts.prefix ? _utils.url.join(this.route, router.opts.prefix, route) : _utils.url.join(this.route, route), '/'); this.log('verbose', `Route "${method}:${endpoint}" is added from app [${this.name}].`); return this; } requireFeatures(features, middleware) { const hasNotEnabled = _utils._.find(_utils._.castArray(features), (feature)=>!this.enabled(feature)); if (hasNotEnabled) { throw new _types.InvalidConfiguration(`Middleware "${middleware}" requires "${hasNotEnabled}" feature to be enabled.`, this, `middlewares.${middleware}`); } } /** * Attach a router to this app module, skipped while the server running in deaf mode * @param {Router} nestedRouter */ addRouter(nestedRouter, baseRoute) { if (this.router == null) { // if mount to server level this.createServerModuleRouter(); } this.router.attach(nestedRouter, baseRoute); return this; } createServerModuleRouter() { if (!this.isServer) { throw new _types.ApplicationError('Only the server instance can create a server module router.'); } this.router = this.engine.createModuleRouter(this); this.engine.mount('/', this.router); } /** * Translate a relative path and query parameters if any to a url path * @param {string} relativePath - Relative path * @param {...*} [pathOrQuery] - Queries * @returns {string} */ toWebPath(relativePath, ...pathOrQuery) { let url, query; if (pathOrQuery && pathOrQuery.length > 0 && (pathOrQuery.length > 1 || pathOrQuery[0] !== undefined)) { if (_utils._.isObject(pathOrQuery[pathOrQuery.length - 1])) { query = pathOrQuery.pop(); } pathOrQuery.unshift(relativePath); url = _utils.url.join(this.route, ...pathOrQuery); } else { url = _utils.url.join(this.route, relativePath); } url = _utils.text.ensureStartsWith(url, '/'); if (query) { url = _utils.url.appendQuery(url, query); url = url.replace('/?', '?'); } return url; } /** * Attach a middleware to a router. * @param {Router} router - The router to attach the middleware * @param {function} middleware - (ctx, next) => () * @param {String} name - The name of the middleware */ useMiddleware(router, middleware, name) { if (typeof middleware !== 'function') { throw new _types.InvalidArgument('Invalid middleware.', { name, middleware }); } router.use(this._wrapMiddlewareTracer(middleware, name)); this.log('verbose', `Attached middleware [${name}].`); } middlewareConfig(config, typeInfo, name) { return this.sanitize(config, typeInfo, name, 'middlewares'); } _wrapMiddlewareTracer(middleware, name) { if (this.options.traceMiddlewares) { return async (ctx, next)=>{ this.log('debug', `Step in middleware "${name || middleware.name}" ...`); let ret = await middleware(ctx, next); this.log('debug', `Step out from middleware "${name || middleware.name}".`); return ret; }; } return middleware; } _getFeatureFallbackPath() { let pathArray = super._getFeatureFallbackPath(); pathArray.splice(1, 0, _nodepath.default.resolve(__dirname, 'webFeatures')); return pathArray; } /** * @param {string} name - The name of the routable instance. * @param {object} [options] - Routable options * @property {string} [options.controllersPath='actions'] - Relative path of controller source files * @property {string} [options.publicPath='public'] - Relative path of front-end static files */ constructor(name, options){ super(name, { ..._defaultServerOpts.defaultRoutableOpts, ...options }); /** * Frontend static files path. * @member {string} **/ this.publicPath = this.toAbsolutePath(this.options.publicPath); this.controllersPath = _nodepath.default.resolve(this.sourcePath, this.options.controllersPath); this.routable = true; } } return _class; }; const _default = Routable; //# sourceMappingURL=Routable.js.map