123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- 'use strict';
- const assert = require('assert');
- const fs = require('fs');
- const KoaApplication = require('koa');
- const EggConsoleLogger = require('egg-logger').EggConsoleLogger;
- const debug = require('debug')('egg-core');
- const is = require('is-type-of');
- const co = require('co');
- const BaseContextClass = require('./utils/base_context_class');
- const utils = require('./utils');
- const Router = require('@eggjs/router').EggRouter;
- const Timing = require('./utils/timing');
- const Lifecycle = require('./lifecycle');
- const DEPRECATE = Symbol('EggCore#deprecate');
- const ROUTER = Symbol('EggCore#router');
- const EGG_LOADER = Symbol.for('egg#loader');
- const CLOSE_PROMISE = Symbol('EggCore#closePromise');
- class EggCore extends KoaApplication {
- /**
- * @class
- * @param {Object} options - options
- * @param {String} [options.baseDir=process.cwd()] - the directory of application
- * @param {String} [options.type=application|agent] - whether it's running in app worker or agent worker
- * @param {Object} [options.plugins] - custom plugins
- * @since 1.0.0
- */
- constructor(options = {}) {
- options.baseDir = options.baseDir || process.cwd();
- options.type = options.type || 'application';
- assert(typeof options.baseDir === 'string', 'options.baseDir required, and must be a string');
- assert(fs.existsSync(options.baseDir), `Directory ${options.baseDir} not exists`);
- assert(fs.statSync(options.baseDir).isDirectory(), `Directory ${options.baseDir} is not a directory`);
- assert(options.type === 'application' || options.type === 'agent', 'options.type should be application or agent');
- super();
- this.timing = new Timing();
- // cache deprecate object by file
- this[DEPRECATE] = new Map();
- /**
- * @member {Object} EggCore#options
- * @private
- * @since 1.0.0
- */
- this._options = this.options = options;
- this.deprecate.property(this, '_options', 'app._options is deprecated, use app.options instead');
- /**
- * logging for EggCore, avoid using console directly
- * @member {Logger} EggCore#console
- * @private
- * @since 1.0.0
- */
- this.console = new EggConsoleLogger();
- /**
- * @member {BaseContextClass} EggCore#BaseContextClass
- * @since 1.0.0
- */
- this.BaseContextClass = BaseContextClass;
- /**
- * Base controller to be extended by controller in `app.controller`
- * @class Controller
- * @extends BaseContextClass
- * @example
- * class UserController extends app.Controller {}
- */
- const Controller = this.BaseContextClass;
- /**
- * Retrieve base controller
- * @member {Controller} EggCore#Controller
- * @since 1.0.0
- */
- this.Controller = Controller;
- /**
- * Base service to be extended by services in `app.service`
- * @class Service
- * @extends BaseContextClass
- * @example
- * class UserService extends app.Service {}
- */
- const Service = this.BaseContextClass;
- /**
- * Retrieve base service
- * @member {Service} EggCore#Service
- * @since 1.0.0
- */
- this.Service = Service;
- this.lifecycle = new Lifecycle({
- baseDir: options.baseDir,
- app: this,
- logger: this.console,
- });
- this.lifecycle.on('error', err => this.emit('error', err));
- this.lifecycle.on('ready_timeout', id => this.emit('ready_timeout', id));
- this.lifecycle.on('ready_stat', data => this.emit('ready_stat', data));
- /**
- * The loader instance, the default class is {@link EggLoader}.
- * If you want define
- * @member {EggLoader} EggCore#loader
- * @since 1.0.0
- */
- const Loader = this[EGG_LOADER];
- assert(Loader, 'Symbol.for(\'egg#loader\') is required');
- this.loader = new Loader({
- baseDir: options.baseDir,
- app: this,
- plugins: options.plugins,
- logger: this.console,
- serverScope: options.serverScope,
- env: options.env,
- });
- }
- /**
- * override koa's app.use, support generator function
- * @param {Function} fn - middleware
- * @return {Application} app
- * @since 1.0.0
- */
- use(fn) {
- assert(is.function(fn), 'app.use() requires a function');
- debug('use %s', fn._name || fn.name || '-');
- this.middleware.push(utils.middleware(fn));
- return this;
- }
- /**
- * Whether `application` or `agent`
- * @member {String}
- * @since 1.0.0
- */
- get type() {
- return this.options.type;
- }
- /**
- * The current directory of application
- * @member {String}
- * @see {@link AppInfo#baseDir}
- * @since 1.0.0
- */
- get baseDir() {
- return this.options.baseDir;
- }
- /**
- * Alias to {@link https://npmjs.com/package/depd}
- * @member {Function}
- * @since 1.0.0
- */
- get deprecate() {
- const caller = utils.getCalleeFromStack();
- if (!this[DEPRECATE].has(caller)) {
- const deprecate = require('depd')('egg');
- // dynamic set _file to caller
- deprecate._file = caller;
- this[DEPRECATE].set(caller, deprecate);
- }
- return this[DEPRECATE].get(caller);
- }
- /**
- * The name of application
- * @member {String}
- * @see {@link AppInfo#name}
- * @since 1.0.0
- */
- get name() {
- return this.loader ? this.loader.pkg.name : '';
- }
- /**
- * Retrieve enabled plugins
- * @member {Object}
- * @since 1.0.0
- */
- get plugins() {
- return this.loader ? this.loader.plugins : {};
- }
- /**
- * The configuration of application
- * @member {Config}
- * @since 1.0.0
- */
- get config() {
- return this.loader ? this.loader.config : {};
- }
- /**
- * Execute scope after loaded and before app start.
- *
- * Notice:
- * This method is now NOT recommanded and reguarded as a deprecated one,
- * For plugin development, we should use `didLoad` instead.
- * For application development, we should use `willReady` instead.
- *
- * @see https://eggjs.org/en/advanced/loader.html#beforestart
- *
- * @param {Function|GeneratorFunction|AsyncFunction} scope function will execute before app start
- */
- beforeStart(scope) {
- this.lifecycle.registerBeforeStart(scope);
- }
- /**
- * register an callback function that will be invoked when application is ready.
- * @see https://github.com/node-modules/ready
- * @since 1.0.0
- * @param {boolean|Error|Function} [flagOrFunction] -
- * @return {Promise|null} return promise when argument is undefined
- * @example
- * const app = new Application(...);
- * app.ready(err => {
- * if (err) throw err;
- * console.log('done');
- * });
- */
- ready(flagOrFunction) {
- return this.lifecycle.ready(flagOrFunction);
- }
- /**
- * If a client starts asynchronously, you can register `readyCallback`,
- * then the application will wait for the callback to ready
- *
- * It will log when the callback is not invoked after 10s
- *
- * Recommend to use {@link EggCore#beforeStart}
- * @since 1.0.0
- *
- * @param {String} name - readyCallback task name
- * @param {object} opts -
- * - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout
- * - {Boolean} [isWeakDep=false] - whether it's a weak dependency
- * @return {Function} - a callback
- * @example
- * const done = app.readyCallback('mysql');
- * mysql.ready(done);
- */
- readyCallback(name, opts) {
- return this.lifecycle.legacyReadyCallback(name, opts);
- }
- /**
- * Register a function that will be called when app close.
- *
- * Notice:
- * This method is now NOT recommanded directly used,
- * Developers SHOULDN'T use app.beforeClose directly now,
- * but in the form of class to implement beforeClose instead.
- *
- * @see https://eggjs.org/en/advanced/loader.html#beforeclose
- *
- * @param {Function} fn - the function that can be generator function or async function.
- */
- beforeClose(fn) {
- this.lifecycle.registerBeforeClose(fn);
- }
- /**
- * Close all, it will close
- * - callbacks registered by beforeClose
- * - emit `close` event
- * - remove add listeners
- *
- * If error is thrown when it's closing, the promise will reject.
- * It will also reject after following call.
- * @return {Promise} promise
- * @since 1.0.0
- */
- async close() {
- if (this[CLOSE_PROMISE]) return this[CLOSE_PROMISE];
- this[CLOSE_PROMISE] = this.lifecycle.close();
- return this[CLOSE_PROMISE];
- }
- /**
- * get router
- * @member {Router} EggCore#router
- * @since 1.0.0
- */
- get router() {
- if (this[ROUTER]) {
- return this[ROUTER];
- }
- const router = this[ROUTER] = new Router({ sensitive: true }, this);
- // register router middleware
- this.beforeStart(() => {
- this.use(router.middleware());
- });
- return router;
- }
- /**
- * Alias to {@link Router#url}
- * @param {String} name - Router name
- * @param {Object} params - more parameters
- * @return {String} url
- */
- url(name, params) {
- return this.router.url(name, params);
- }
- del(...args) {
- this.router.delete(...args);
- return this;
- }
- get [EGG_LOADER]() {
- return require('./loader/egg_loader');
- }
- /**
- * Convert a generator function to a promisable one.
- *
- * Notice: for other kinds of functions, it directly returns you what it is.
- *
- * @param {Function} fn The inputted function.
- * @return {AsyncFunction} An async promise-based function.
- * @example
- ```javascript
- const fn = function* (arg) {
- return arg;
- };
- const wrapped = app.toAsyncFunction(fn);
- wrapped(true).then((value) => console.log(value));
- ```
- */
- toAsyncFunction(fn) {
- if (!is.generatorFunction(fn)) return fn;
- fn = co.wrap(fn);
- return async function(...args) {
- return fn.apply(this, args);
- };
- }
- /**
- * Convert an object with generator functions to a Promisable one.
- * @param {Mixed} obj The inputted object.
- * @return {Promise} A Promisable result.
- * @example
- ```javascript
- const fn = function* (arg) {
- return arg;
- };
- const arr = [ fn(1), fn(2) ];
- const promise = app.toPromise(arr);
- promise.then(res => console.log(res));
- ```
- */
- toPromise(obj) {
- return co(function* () {
- return yield obj;
- });
- }
- }
- // delegate all router method to application
- utils.methods.concat([ 'all', 'resources', 'register', 'redirect' ]).forEach(method => {
- EggCore.prototype[method] = function(...args) {
- this.router[method](...args);
- return this;
- };
- });
- module.exports = EggCore;
|