applyHooks.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. 'use strict';
  2. const symbols = require('../../schema/symbols');
  3. const promiseOrCallback = require('../promiseOrCallback');
  4. /*!
  5. * ignore
  6. */
  7. module.exports = applyHooks;
  8. /*!
  9. * ignore
  10. */
  11. applyHooks.middlewareFunctions = [
  12. 'deleteOne',
  13. 'save',
  14. 'validate',
  15. 'remove',
  16. 'updateOne',
  17. 'init'
  18. ];
  19. /*!
  20. * Register hooks for this model
  21. *
  22. * @param {Model} model
  23. * @param {Schema} schema
  24. */
  25. function applyHooks(model, schema, options) {
  26. options = options || {};
  27. const kareemOptions = {
  28. useErrorHandlers: true,
  29. numCallbackParams: 1,
  30. nullResultByDefault: true,
  31. contextParameter: true
  32. };
  33. const objToDecorate = options.decorateDoc ? model : model.prototype;
  34. model.$appliedHooks = true;
  35. for (const key of Object.keys(schema.paths)) {
  36. const type = schema.paths[key];
  37. let childModel = null;
  38. if (type.$isSingleNested) {
  39. childModel = type.caster;
  40. } else if (type.$isMongooseDocumentArray) {
  41. childModel = type.Constructor;
  42. } else {
  43. continue;
  44. }
  45. if (childModel.$appliedHooks) {
  46. continue;
  47. }
  48. applyHooks(childModel, type.schema, options);
  49. if (childModel.discriminators != null) {
  50. const keys = Object.keys(childModel.discriminators);
  51. for (const key of keys) {
  52. applyHooks(childModel.discriminators[key],
  53. childModel.discriminators[key].schema, options);
  54. }
  55. }
  56. }
  57. // Built-in hooks rely on hooking internal functions in order to support
  58. // promises and make it so that `doc.save.toString()` provides meaningful
  59. // information.
  60. const middleware = schema.s.hooks.
  61. filter(hook => {
  62. if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
  63. return !!hook['document'];
  64. }
  65. if (hook.name === 'remove' || hook.name === 'init') {
  66. return hook['document'] == null || !!hook['document'];
  67. }
  68. if (hook.query != null || hook.document != null) {
  69. return hook.document !== false;
  70. }
  71. return true;
  72. }).
  73. filter(hook => {
  74. // If user has overwritten the method, don't apply built-in middleware
  75. if (schema.methods[hook.name]) {
  76. return !hook.fn[symbols.builtInMiddleware];
  77. }
  78. return true;
  79. });
  80. model._middleware = middleware;
  81. objToDecorate.$__originalValidate = objToDecorate.$__originalValidate || objToDecorate.$__validate;
  82. for (const method of ['save', 'validate', 'remove', 'deleteOne']) {
  83. const toWrap = method === 'validate' ? '$__originalValidate' : `$__${method}`;
  84. const wrapped = middleware.
  85. createWrapper(method, objToDecorate[toWrap], null, kareemOptions);
  86. objToDecorate[`$__${method}`] = wrapped;
  87. }
  88. objToDecorate.$__init = middleware.
  89. createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
  90. // Support hooks for custom methods
  91. const customMethods = Object.keys(schema.methods);
  92. const customMethodOptions = Object.assign({}, kareemOptions, {
  93. // Only use `checkForPromise` for custom methods, because mongoose
  94. // query thunks are not as consistent as I would like about returning
  95. // a nullish value rather than the query. If a query thunk returns
  96. // a query, `checkForPromise` causes infinite recursion
  97. checkForPromise: true
  98. });
  99. for (const method of customMethods) {
  100. if (!middleware.hasHooks(method)) {
  101. // Don't wrap if there are no hooks for the custom method to avoid
  102. // surprises. Also, `createWrapper()` enforces consistent async,
  103. // so wrapping a sync method would break it.
  104. continue;
  105. }
  106. const originalMethod = objToDecorate[method];
  107. objToDecorate[method] = function() {
  108. const args = Array.prototype.slice.call(arguments);
  109. const cb = args.slice(-1).pop();
  110. const argsWithoutCallback = typeof cb === 'function' ?
  111. args.slice(0, args.length - 1) : args;
  112. return promiseOrCallback(cb, callback => {
  113. return this[`$__${method}`].apply(this,
  114. argsWithoutCallback.concat([callback]));
  115. }, model.events);
  116. };
  117. objToDecorate[`$__${method}`] = middleware.
  118. createWrapper(method, originalMethod, null, customMethodOptions);
  119. }
  120. }