applyHooks.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. 'use strict';
  2. /*!
  3. * ignore
  4. */
  5. module.exports = applyHooks;
  6. /*!
  7. * ignore
  8. */
  9. applyHooks.middlewareFunctions = [
  10. 'deleteOne',
  11. 'remove',
  12. 'save',
  13. 'updateOne',
  14. 'validate',
  15. 'init'
  16. ];
  17. /*!
  18. * ignore
  19. */
  20. const alreadyHookedFunctions = new Set(applyHooks.middlewareFunctions.flatMap(fn => ([fn, `$__${fn}`])));
  21. /**
  22. * Register hooks for this model
  23. *
  24. * @param {Model} model
  25. * @param {Schema} schema
  26. * @param {Object} options
  27. * @api private
  28. */
  29. function applyHooks(model, schema, options) {
  30. options = options || {};
  31. const kareemOptions = {
  32. useErrorHandlers: true,
  33. numCallbackParams: 1,
  34. nullResultByDefault: true,
  35. contextParameter: true
  36. };
  37. const objToDecorate = options.decorateDoc ? model : model.prototype;
  38. model.$appliedHooks = true;
  39. for (const key of Object.keys(schema.paths)) {
  40. let type = schema.paths[key];
  41. let childModel = null;
  42. const result = findChildModel(type);
  43. if (result) {
  44. childModel = result.childModel;
  45. type = result.type;
  46. } else {
  47. continue;
  48. }
  49. if (childModel.$appliedHooks) {
  50. continue;
  51. }
  52. applyHooks(childModel, type.schema, {
  53. ...options,
  54. decorateDoc: false,
  55. isChildSchema: true
  56. });
  57. if (childModel.discriminators != null) {
  58. const keys = Object.keys(childModel.discriminators);
  59. for (const key of keys) {
  60. applyHooks(childModel.discriminators[key],
  61. childModel.discriminators[key].schema, options);
  62. }
  63. }
  64. }
  65. // Built-in hooks rely on hooking internal functions in order to support
  66. // promises and make it so that `doc.save.toString()` provides meaningful
  67. // information.
  68. const middleware = schema._getDocumentMiddleware();
  69. model._middleware = middleware;
  70. objToDecorate.$__init = middleware.
  71. createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
  72. // Support hooks for custom methods
  73. const customMethods = Object.keys(schema.methods);
  74. const customMethodOptions = Object.assign({}, kareemOptions, {
  75. // Only use `checkForPromise` for custom methods, because mongoose
  76. // query thunks are not as consistent as I would like about returning
  77. // a nullish value rather than the query. If a query thunk returns
  78. // a query, `checkForPromise` causes infinite recursion
  79. checkForPromise: true
  80. });
  81. for (const method of customMethods) {
  82. if (alreadyHookedFunctions.has(method)) {
  83. continue;
  84. }
  85. if (!middleware.hasHooks(method)) {
  86. // Don't wrap if there are no hooks for the custom method to avoid
  87. // surprises. Also, `createWrapper()` enforces consistent async,
  88. // so wrapping a sync method would break it.
  89. continue;
  90. }
  91. const originalMethod = objToDecorate[method];
  92. objToDecorate[`$__${method}`] = objToDecorate[method];
  93. objToDecorate[method] = middleware.
  94. createWrapper(method, originalMethod, null, customMethodOptions);
  95. }
  96. }
  97. /**
  98. * Check if there is an embedded schematype in the given schematype. Handles drilling down into primitive
  99. * arrays and maps in case of array of array of subdocs or map of subdocs.
  100. *
  101. * @param {SchemaType} curType
  102. * @returns {{ childModel: Model | typeof Subdocument, curType: SchemaType } | null}
  103. */
  104. function findChildModel(curType) {
  105. if (curType.$isSingleNested || curType.$isMongooseDocumentArray) {
  106. return { childModel: curType.Constructor, type: curType };
  107. }
  108. if (curType.instance === 'Array') {
  109. const embedded = curType.getEmbeddedSchemaType();
  110. if (embedded) {
  111. return findChildModel(embedded);
  112. }
  113. }
  114. if (curType.instance === 'Map') {
  115. const mapType = curType.getEmbeddedSchemaType();
  116. if (mapType) {
  117. return findChildModel(mapType);
  118. }
  119. }
  120. return null;
  121. }