egg.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. 'use strict';
  2. const assert = require('assert');
  3. const fs = require('fs');
  4. const KoaApplication = require('koa');
  5. const EggConsoleLogger = require('egg-logger').EggConsoleLogger;
  6. const debug = require('debug')('egg-core');
  7. const is = require('is-type-of');
  8. const co = require('co');
  9. const BaseContextClass = require('./utils/base_context_class');
  10. const utils = require('./utils');
  11. const Router = require('@eggjs/router').EggRouter;
  12. const Timing = require('./utils/timing');
  13. const Lifecycle = require('./lifecycle');
  14. const DEPRECATE = Symbol('EggCore#deprecate');
  15. const ROUTER = Symbol('EggCore#router');
  16. const EGG_LOADER = Symbol.for('egg#loader');
  17. const CLOSE_PROMISE = Symbol('EggCore#closePromise');
  18. class EggCore extends KoaApplication {
  19. /**
  20. * @class
  21. * @param {Object} options - options
  22. * @param {String} [options.baseDir=process.cwd()] - the directory of application
  23. * @param {String} [options.type=application|agent] - whether it's running in app worker or agent worker
  24. * @param {Object} [options.plugins] - custom plugins
  25. * @since 1.0.0
  26. */
  27. constructor(options = {}) {
  28. options.baseDir = options.baseDir || process.cwd();
  29. options.type = options.type || 'application';
  30. assert(typeof options.baseDir === 'string', 'options.baseDir required, and must be a string');
  31. assert(fs.existsSync(options.baseDir), `Directory ${options.baseDir} not exists`);
  32. assert(fs.statSync(options.baseDir).isDirectory(), `Directory ${options.baseDir} is not a directory`);
  33. assert(options.type === 'application' || options.type === 'agent', 'options.type should be application or agent');
  34. super();
  35. this.timing = new Timing();
  36. // cache deprecate object by file
  37. this[DEPRECATE] = new Map();
  38. /**
  39. * @member {Object} EggCore#options
  40. * @private
  41. * @since 1.0.0
  42. */
  43. this._options = this.options = options;
  44. this.deprecate.property(this, '_options', 'app._options is deprecated, use app.options instead');
  45. /**
  46. * logging for EggCore, avoid using console directly
  47. * @member {Logger} EggCore#console
  48. * @private
  49. * @since 1.0.0
  50. */
  51. this.console = new EggConsoleLogger();
  52. /**
  53. * @member {BaseContextClass} EggCore#BaseContextClass
  54. * @since 1.0.0
  55. */
  56. this.BaseContextClass = BaseContextClass;
  57. /**
  58. * Base controller to be extended by controller in `app.controller`
  59. * @class Controller
  60. * @extends BaseContextClass
  61. * @example
  62. * class UserController extends app.Controller {}
  63. */
  64. const Controller = this.BaseContextClass;
  65. /**
  66. * Retrieve base controller
  67. * @member {Controller} EggCore#Controller
  68. * @since 1.0.0
  69. */
  70. this.Controller = Controller;
  71. /**
  72. * Base service to be extended by services in `app.service`
  73. * @class Service
  74. * @extends BaseContextClass
  75. * @example
  76. * class UserService extends app.Service {}
  77. */
  78. const Service = this.BaseContextClass;
  79. /**
  80. * Retrieve base service
  81. * @member {Service} EggCore#Service
  82. * @since 1.0.0
  83. */
  84. this.Service = Service;
  85. this.lifecycle = new Lifecycle({
  86. baseDir: options.baseDir,
  87. app: this,
  88. logger: this.console,
  89. });
  90. this.lifecycle.on('error', err => this.emit('error', err));
  91. this.lifecycle.on('ready_timeout', id => this.emit('ready_timeout', id));
  92. this.lifecycle.on('ready_stat', data => this.emit('ready_stat', data));
  93. /**
  94. * The loader instance, the default class is {@link EggLoader}.
  95. * If you want define
  96. * @member {EggLoader} EggCore#loader
  97. * @since 1.0.0
  98. */
  99. const Loader = this[EGG_LOADER];
  100. assert(Loader, 'Symbol.for(\'egg#loader\') is required');
  101. this.loader = new Loader({
  102. baseDir: options.baseDir,
  103. app: this,
  104. plugins: options.plugins,
  105. logger: this.console,
  106. serverScope: options.serverScope,
  107. env: options.env,
  108. });
  109. }
  110. /**
  111. * override koa's app.use, support generator function
  112. * @param {Function} fn - middleware
  113. * @return {Application} app
  114. * @since 1.0.0
  115. */
  116. use(fn) {
  117. assert(is.function(fn), 'app.use() requires a function');
  118. debug('use %s', fn._name || fn.name || '-');
  119. this.middleware.push(utils.middleware(fn));
  120. return this;
  121. }
  122. /**
  123. * Whether `application` or `agent`
  124. * @member {String}
  125. * @since 1.0.0
  126. */
  127. get type() {
  128. return this.options.type;
  129. }
  130. /**
  131. * The current directory of application
  132. * @member {String}
  133. * @see {@link AppInfo#baseDir}
  134. * @since 1.0.0
  135. */
  136. get baseDir() {
  137. return this.options.baseDir;
  138. }
  139. /**
  140. * Alias to {@link https://npmjs.com/package/depd}
  141. * @member {Function}
  142. * @since 1.0.0
  143. */
  144. get deprecate() {
  145. const caller = utils.getCalleeFromStack();
  146. if (!this[DEPRECATE].has(caller)) {
  147. const deprecate = require('depd')('egg');
  148. // dynamic set _file to caller
  149. deprecate._file = caller;
  150. this[DEPRECATE].set(caller, deprecate);
  151. }
  152. return this[DEPRECATE].get(caller);
  153. }
  154. /**
  155. * The name of application
  156. * @member {String}
  157. * @see {@link AppInfo#name}
  158. * @since 1.0.0
  159. */
  160. get name() {
  161. return this.loader ? this.loader.pkg.name : '';
  162. }
  163. /**
  164. * Retrieve enabled plugins
  165. * @member {Object}
  166. * @since 1.0.0
  167. */
  168. get plugins() {
  169. return this.loader ? this.loader.plugins : {};
  170. }
  171. /**
  172. * The configuration of application
  173. * @member {Config}
  174. * @since 1.0.0
  175. */
  176. get config() {
  177. return this.loader ? this.loader.config : {};
  178. }
  179. /**
  180. * Execute scope after loaded and before app start.
  181. *
  182. * Notice:
  183. * This method is now NOT recommanded and reguarded as a deprecated one,
  184. * For plugin development, we should use `didLoad` instead.
  185. * For application development, we should use `willReady` instead.
  186. *
  187. * @see https://eggjs.org/en/advanced/loader.html#beforestart
  188. *
  189. * @param {Function|GeneratorFunction|AsyncFunction} scope function will execute before app start
  190. */
  191. beforeStart(scope) {
  192. this.lifecycle.registerBeforeStart(scope);
  193. }
  194. /**
  195. * register an callback function that will be invoked when application is ready.
  196. * @see https://github.com/node-modules/ready
  197. * @since 1.0.0
  198. * @param {boolean|Error|Function} [flagOrFunction] -
  199. * @return {Promise|null} return promise when argument is undefined
  200. * @example
  201. * const app = new Application(...);
  202. * app.ready(err => {
  203. * if (err) throw err;
  204. * console.log('done');
  205. * });
  206. */
  207. ready(flagOrFunction) {
  208. return this.lifecycle.ready(flagOrFunction);
  209. }
  210. /**
  211. * If a client starts asynchronously, you can register `readyCallback`,
  212. * then the application will wait for the callback to ready
  213. *
  214. * It will log when the callback is not invoked after 10s
  215. *
  216. * Recommend to use {@link EggCore#beforeStart}
  217. * @since 1.0.0
  218. *
  219. * @param {String} name - readyCallback task name
  220. * @param {object} opts -
  221. * - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout
  222. * - {Boolean} [isWeakDep=false] - whether it's a weak dependency
  223. * @return {Function} - a callback
  224. * @example
  225. * const done = app.readyCallback('mysql');
  226. * mysql.ready(done);
  227. */
  228. readyCallback(name, opts) {
  229. return this.lifecycle.legacyReadyCallback(name, opts);
  230. }
  231. /**
  232. * Register a function that will be called when app close.
  233. *
  234. * Notice:
  235. * This method is now NOT recommanded directly used,
  236. * Developers SHOULDN'T use app.beforeClose directly now,
  237. * but in the form of class to implement beforeClose instead.
  238. *
  239. * @see https://eggjs.org/en/advanced/loader.html#beforeclose
  240. *
  241. * @param {Function} fn - the function that can be generator function or async function.
  242. */
  243. beforeClose(fn) {
  244. this.lifecycle.registerBeforeClose(fn);
  245. }
  246. /**
  247. * Close all, it will close
  248. * - callbacks registered by beforeClose
  249. * - emit `close` event
  250. * - remove add listeners
  251. *
  252. * If error is thrown when it's closing, the promise will reject.
  253. * It will also reject after following call.
  254. * @return {Promise} promise
  255. * @since 1.0.0
  256. */
  257. async close() {
  258. if (this[CLOSE_PROMISE]) return this[CLOSE_PROMISE];
  259. this[CLOSE_PROMISE] = this.lifecycle.close();
  260. return this[CLOSE_PROMISE];
  261. }
  262. /**
  263. * get router
  264. * @member {Router} EggCore#router
  265. * @since 1.0.0
  266. */
  267. get router() {
  268. if (this[ROUTER]) {
  269. return this[ROUTER];
  270. }
  271. const router = this[ROUTER] = new Router({ sensitive: true }, this);
  272. // register router middleware
  273. this.beforeStart(() => {
  274. this.use(router.middleware());
  275. });
  276. return router;
  277. }
  278. /**
  279. * Alias to {@link Router#url}
  280. * @param {String} name - Router name
  281. * @param {Object} params - more parameters
  282. * @return {String} url
  283. */
  284. url(name, params) {
  285. return this.router.url(name, params);
  286. }
  287. del(...args) {
  288. this.router.delete(...args);
  289. return this;
  290. }
  291. get [EGG_LOADER]() {
  292. return require('./loader/egg_loader');
  293. }
  294. /**
  295. * Convert a generator function to a promisable one.
  296. *
  297. * Notice: for other kinds of functions, it directly returns you what it is.
  298. *
  299. * @param {Function} fn The inputted function.
  300. * @return {AsyncFunction} An async promise-based function.
  301. * @example
  302. ```javascript
  303. const fn = function* (arg) {
  304. return arg;
  305. };
  306. const wrapped = app.toAsyncFunction(fn);
  307. wrapped(true).then((value) => console.log(value));
  308. ```
  309. */
  310. toAsyncFunction(fn) {
  311. if (!is.generatorFunction(fn)) return fn;
  312. fn = co.wrap(fn);
  313. return async function(...args) {
  314. return fn.apply(this, args);
  315. };
  316. }
  317. /**
  318. * Convert an object with generator functions to a Promisable one.
  319. * @param {Mixed} obj The inputted object.
  320. * @return {Promise} A Promisable result.
  321. * @example
  322. ```javascript
  323. const fn = function* (arg) {
  324. return arg;
  325. };
  326. const arr = [ fn(1), fn(2) ];
  327. const promise = app.toPromise(arr);
  328. promise.then(res => console.log(res));
  329. ```
  330. */
  331. toPromise(obj) {
  332. return co(function* () {
  333. return yield obj;
  334. });
  335. }
  336. }
  337. // delegate all router method to application
  338. utils.methods.concat([ 'all', 'resources', 'register', 'redirect' ]).forEach(method => {
  339. EggCore.prototype[method] = function(...args) {
  340. this.router[method](...args);
  341. return this;
  342. };
  343. });
  344. module.exports = EggCore;