@depup/mongoose 9.1.3-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +397 -0
  3. package/SECURITY.md +1 -0
  4. package/eslint.config.mjs +198 -0
  5. package/index.js +64 -0
  6. package/lib/aggregate.js +1189 -0
  7. package/lib/cast/bigint.js +46 -0
  8. package/lib/cast/boolean.js +32 -0
  9. package/lib/cast/date.js +41 -0
  10. package/lib/cast/decimal128.js +39 -0
  11. package/lib/cast/double.js +50 -0
  12. package/lib/cast/int32.js +36 -0
  13. package/lib/cast/number.js +42 -0
  14. package/lib/cast/objectid.js +29 -0
  15. package/lib/cast/string.js +37 -0
  16. package/lib/cast/uuid.js +35 -0
  17. package/lib/cast.js +436 -0
  18. package/lib/collection.js +321 -0
  19. package/lib/connection.js +1855 -0
  20. package/lib/connectionState.js +26 -0
  21. package/lib/constants.js +73 -0
  22. package/lib/cursor/aggregationCursor.js +466 -0
  23. package/lib/cursor/changeStream.js +198 -0
  24. package/lib/cursor/queryCursor.js +622 -0
  25. package/lib/document.js +5521 -0
  26. package/lib/driver.js +15 -0
  27. package/lib/drivers/SPEC.md +4 -0
  28. package/lib/drivers/node-mongodb-native/bulkWriteResult.js +5 -0
  29. package/lib/drivers/node-mongodb-native/collection.js +393 -0
  30. package/lib/drivers/node-mongodb-native/connection.js +506 -0
  31. package/lib/drivers/node-mongodb-native/index.js +10 -0
  32. package/lib/error/browserMissingSchema.js +29 -0
  33. package/lib/error/bulkSaveIncompleteError.js +44 -0
  34. package/lib/error/bulkWriteError.js +41 -0
  35. package/lib/error/cast.js +158 -0
  36. package/lib/error/createCollectionsError.js +26 -0
  37. package/lib/error/divergentArray.js +40 -0
  38. package/lib/error/eachAsyncMultiError.js +41 -0
  39. package/lib/error/index.js +237 -0
  40. package/lib/error/invalidSchemaOption.js +32 -0
  41. package/lib/error/messages.js +47 -0
  42. package/lib/error/missingSchema.js +33 -0
  43. package/lib/error/mongooseError.js +13 -0
  44. package/lib/error/notFound.js +47 -0
  45. package/lib/error/objectExpected.js +31 -0
  46. package/lib/error/objectParameter.js +31 -0
  47. package/lib/error/overwriteModel.js +31 -0
  48. package/lib/error/parallelSave.js +33 -0
  49. package/lib/error/parallelValidate.js +33 -0
  50. package/lib/error/serverSelection.js +62 -0
  51. package/lib/error/setOptionError.js +103 -0
  52. package/lib/error/strict.js +35 -0
  53. package/lib/error/strictPopulate.js +31 -0
  54. package/lib/error/syncIndexes.js +30 -0
  55. package/lib/error/validation.js +97 -0
  56. package/lib/error/validator.js +100 -0
  57. package/lib/error/version.js +38 -0
  58. package/lib/helpers/aggregate/prepareDiscriminatorPipeline.js +39 -0
  59. package/lib/helpers/aggregate/stringifyFunctionOperators.js +50 -0
  60. package/lib/helpers/arrayDepth.js +33 -0
  61. package/lib/helpers/clone.js +204 -0
  62. package/lib/helpers/common.js +127 -0
  63. package/lib/helpers/createJSONSchemaTypeDefinition.js +24 -0
  64. package/lib/helpers/cursor/eachAsync.js +225 -0
  65. package/lib/helpers/discriminator/applyEmbeddedDiscriminators.js +36 -0
  66. package/lib/helpers/discriminator/areDiscriminatorValuesEqual.js +16 -0
  67. package/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js +12 -0
  68. package/lib/helpers/discriminator/getConstructor.js +29 -0
  69. package/lib/helpers/discriminator/getDiscriminatorByValue.js +28 -0
  70. package/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js +27 -0
  71. package/lib/helpers/discriminator/mergeDiscriminatorSchema.js +91 -0
  72. package/lib/helpers/document/applyDefaults.js +132 -0
  73. package/lib/helpers/document/applyTimestamps.js +106 -0
  74. package/lib/helpers/document/applyVirtuals.js +147 -0
  75. package/lib/helpers/document/cleanModifiedSubpaths.js +45 -0
  76. package/lib/helpers/document/compile.js +238 -0
  77. package/lib/helpers/document/getDeepestSubdocumentForPath.js +38 -0
  78. package/lib/helpers/document/getEmbeddedDiscriminatorPath.js +53 -0
  79. package/lib/helpers/document/handleSpreadDoc.js +35 -0
  80. package/lib/helpers/each.js +25 -0
  81. package/lib/helpers/error/combinePathErrors.js +22 -0
  82. package/lib/helpers/firstKey.js +8 -0
  83. package/lib/helpers/get.js +65 -0
  84. package/lib/helpers/getConstructorName.js +16 -0
  85. package/lib/helpers/getDefaultBulkwriteResult.js +18 -0
  86. package/lib/helpers/getFunctionName.js +10 -0
  87. package/lib/helpers/immediate.js +16 -0
  88. package/lib/helpers/indexes/applySchemaCollation.js +13 -0
  89. package/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js +14 -0
  90. package/lib/helpers/indexes/getRelatedIndexes.js +63 -0
  91. package/lib/helpers/indexes/isDefaultIdIndex.js +18 -0
  92. package/lib/helpers/indexes/isIndexEqual.js +95 -0
  93. package/lib/helpers/indexes/isIndexSpecEqual.js +32 -0
  94. package/lib/helpers/indexes/isTextIndex.js +16 -0
  95. package/lib/helpers/indexes/isTimeseriesIndex.js +16 -0
  96. package/lib/helpers/isAsyncFunction.js +9 -0
  97. package/lib/helpers/isBsonType.js +15 -0
  98. package/lib/helpers/isMongooseObject.js +22 -0
  99. package/lib/helpers/isObject.js +16 -0
  100. package/lib/helpers/isPOJO.js +12 -0
  101. package/lib/helpers/isPromise.js +6 -0
  102. package/lib/helpers/isSimpleValidator.js +22 -0
  103. package/lib/helpers/minimize.js +41 -0
  104. package/lib/helpers/model/applyDefaultsToPOJO.js +52 -0
  105. package/lib/helpers/model/applyHooks.js +140 -0
  106. package/lib/helpers/model/applyMethods.js +70 -0
  107. package/lib/helpers/model/applyStaticHooks.js +33 -0
  108. package/lib/helpers/model/applyStatics.js +13 -0
  109. package/lib/helpers/model/castBulkWrite.js +316 -0
  110. package/lib/helpers/model/decorateBulkWriteResult.js +8 -0
  111. package/lib/helpers/model/discriminator.js +265 -0
  112. package/lib/helpers/model/pushNestedArrayPaths.js +15 -0
  113. package/lib/helpers/omitUndefined.js +20 -0
  114. package/lib/helpers/once.js +12 -0
  115. package/lib/helpers/parallelLimit.js +37 -0
  116. package/lib/helpers/path/parentPaths.js +18 -0
  117. package/lib/helpers/path/setDottedPath.js +33 -0
  118. package/lib/helpers/pluralize.js +95 -0
  119. package/lib/helpers/populate/assignRawDocsToIdStructure.js +129 -0
  120. package/lib/helpers/populate/assignVals.js +360 -0
  121. package/lib/helpers/populate/createPopulateQueryFilter.js +97 -0
  122. package/lib/helpers/populate/getModelsMapForPopulate.js +776 -0
  123. package/lib/helpers/populate/getSchemaTypes.js +228 -0
  124. package/lib/helpers/populate/getVirtual.js +103 -0
  125. package/lib/helpers/populate/leanPopulateMap.js +7 -0
  126. package/lib/helpers/populate/lookupLocalFields.js +40 -0
  127. package/lib/helpers/populate/markArraySubdocsPopulated.js +49 -0
  128. package/lib/helpers/populate/modelNamesFromRefPath.js +66 -0
  129. package/lib/helpers/populate/removeDeselectedForeignField.js +31 -0
  130. package/lib/helpers/populate/setPopulatedVirtualValue.js +33 -0
  131. package/lib/helpers/populate/skipPopulateValue.js +10 -0
  132. package/lib/helpers/populate/validateRef.js +19 -0
  133. package/lib/helpers/printJestWarning.js +21 -0
  134. package/lib/helpers/processConnectionOptions.js +65 -0
  135. package/lib/helpers/projection/applyProjection.js +83 -0
  136. package/lib/helpers/projection/hasIncludedChildren.js +41 -0
  137. package/lib/helpers/projection/isDefiningProjection.js +18 -0
  138. package/lib/helpers/projection/isExclusive.js +37 -0
  139. package/lib/helpers/projection/isInclusive.js +39 -0
  140. package/lib/helpers/projection/isNestedProjection.js +8 -0
  141. package/lib/helpers/projection/isPathExcluded.js +40 -0
  142. package/lib/helpers/projection/isPathSelectedInclusive.js +28 -0
  143. package/lib/helpers/projection/isSubpath.js +14 -0
  144. package/lib/helpers/projection/parseProjection.js +33 -0
  145. package/lib/helpers/query/applyGlobalOption.js +29 -0
  146. package/lib/helpers/query/cast$expr.js +287 -0
  147. package/lib/helpers/query/castFilterPath.js +54 -0
  148. package/lib/helpers/query/castUpdate.js +643 -0
  149. package/lib/helpers/query/getEmbeddedDiscriminatorPath.js +103 -0
  150. package/lib/helpers/query/handleImmutable.js +44 -0
  151. package/lib/helpers/query/handleReadPreferenceAliases.js +23 -0
  152. package/lib/helpers/query/hasDollarKeys.js +23 -0
  153. package/lib/helpers/query/isOperator.js +14 -0
  154. package/lib/helpers/query/sanitizeFilter.js +38 -0
  155. package/lib/helpers/query/sanitizeProjection.js +14 -0
  156. package/lib/helpers/query/selectPopulatedFields.js +62 -0
  157. package/lib/helpers/query/trusted.js +13 -0
  158. package/lib/helpers/query/validOps.js +3 -0
  159. package/lib/helpers/schema/addAutoId.js +7 -0
  160. package/lib/helpers/schema/applyBuiltinPlugins.js +12 -0
  161. package/lib/helpers/schema/applyPlugins.js +55 -0
  162. package/lib/helpers/schema/applyReadConcern.js +20 -0
  163. package/lib/helpers/schema/applyWriteConcern.js +39 -0
  164. package/lib/helpers/schema/cleanPositionalOperators.js +12 -0
  165. package/lib/helpers/schema/getIndexes.js +171 -0
  166. package/lib/helpers/schema/getKeysInSchemaOrder.js +28 -0
  167. package/lib/helpers/schema/getPath.js +43 -0
  168. package/lib/helpers/schema/getSubdocumentStrictValue.js +32 -0
  169. package/lib/helpers/schema/handleIdOption.js +20 -0
  170. package/lib/helpers/schema/handleTimestampOption.js +24 -0
  171. package/lib/helpers/schema/idGetter.js +34 -0
  172. package/lib/helpers/schema/merge.js +36 -0
  173. package/lib/helpers/schematype/handleImmutable.js +50 -0
  174. package/lib/helpers/setDefaultsOnInsert.js +158 -0
  175. package/lib/helpers/specialProperties.js +3 -0
  176. package/lib/helpers/symbols.js +20 -0
  177. package/lib/helpers/timers.js +3 -0
  178. package/lib/helpers/timestamps/setDocumentTimestamps.js +26 -0
  179. package/lib/helpers/timestamps/setupTimestamps.js +116 -0
  180. package/lib/helpers/topology/allServersUnknown.js +12 -0
  181. package/lib/helpers/topology/isAtlas.js +31 -0
  182. package/lib/helpers/topology/isSSLError.js +16 -0
  183. package/lib/helpers/update/applyTimestampsToChildren.js +193 -0
  184. package/lib/helpers/update/applyTimestampsToUpdate.js +131 -0
  185. package/lib/helpers/update/castArrayFilters.js +113 -0
  186. package/lib/helpers/update/decorateUpdateWithVersionKey.js +35 -0
  187. package/lib/helpers/update/modifiedPaths.js +33 -0
  188. package/lib/helpers/update/moveImmutableProperties.js +53 -0
  189. package/lib/helpers/update/removeUnusedArrayFilters.js +32 -0
  190. package/lib/helpers/update/updatedPathsByArrayFilter.js +27 -0
  191. package/lib/helpers/updateValidators.js +193 -0
  192. package/lib/index.js +17 -0
  193. package/lib/internal.js +46 -0
  194. package/lib/model.js +5010 -0
  195. package/lib/modifiedPathsSnapshot.js +9 -0
  196. package/lib/mongoose.js +1411 -0
  197. package/lib/options/populateOptions.js +36 -0
  198. package/lib/options/propertyOptions.js +8 -0
  199. package/lib/options/saveOptions.js +16 -0
  200. package/lib/options/schemaArrayOptions.js +78 -0
  201. package/lib/options/schemaBufferOptions.js +38 -0
  202. package/lib/options/schemaDateOptions.js +71 -0
  203. package/lib/options/schemaDocumentArrayOptions.js +68 -0
  204. package/lib/options/schemaMapOptions.js +43 -0
  205. package/lib/options/schemaNumberOptions.js +101 -0
  206. package/lib/options/schemaObjectIdOptions.js +64 -0
  207. package/lib/options/schemaStringOptions.js +138 -0
  208. package/lib/options/schemaSubdocumentOptions.js +66 -0
  209. package/lib/options/schemaTypeOptions.js +244 -0
  210. package/lib/options/schemaUnionOptions.js +32 -0
  211. package/lib/options/virtualOptions.js +164 -0
  212. package/lib/options.js +17 -0
  213. package/lib/plugins/index.js +6 -0
  214. package/lib/plugins/saveSubdocs.js +76 -0
  215. package/lib/plugins/sharding.js +84 -0
  216. package/lib/plugins/trackTransaction.js +84 -0
  217. package/lib/plugins/validateBeforeSave.js +41 -0
  218. package/lib/query.js +5673 -0
  219. package/lib/queryHelpers.js +387 -0
  220. package/lib/schema/array.js +699 -0
  221. package/lib/schema/bigint.js +282 -0
  222. package/lib/schema/boolean.js +332 -0
  223. package/lib/schema/buffer.js +343 -0
  224. package/lib/schema/date.js +467 -0
  225. package/lib/schema/decimal128.js +263 -0
  226. package/lib/schema/documentArray.js +656 -0
  227. package/lib/schema/documentArrayElement.js +137 -0
  228. package/lib/schema/double.js +246 -0
  229. package/lib/schema/index.js +32 -0
  230. package/lib/schema/int32.js +289 -0
  231. package/lib/schema/map.js +201 -0
  232. package/lib/schema/mixed.js +146 -0
  233. package/lib/schema/number.js +510 -0
  234. package/lib/schema/objectId.js +333 -0
  235. package/lib/schema/operators/bitwise.js +38 -0
  236. package/lib/schema/operators/exists.js +12 -0
  237. package/lib/schema/operators/geospatial.js +107 -0
  238. package/lib/schema/operators/helpers.js +32 -0
  239. package/lib/schema/operators/text.js +39 -0
  240. package/lib/schema/operators/type.js +20 -0
  241. package/lib/schema/string.js +733 -0
  242. package/lib/schema/subdocument.js +436 -0
  243. package/lib/schema/symbols.js +5 -0
  244. package/lib/schema/union.js +113 -0
  245. package/lib/schema/uuid.js +305 -0
  246. package/lib/schema.js +3226 -0
  247. package/lib/schemaType.js +1835 -0
  248. package/lib/stateMachine.js +232 -0
  249. package/lib/types/array/index.js +119 -0
  250. package/lib/types/array/isMongooseArray.js +5 -0
  251. package/lib/types/array/methods/index.js +1095 -0
  252. package/lib/types/arraySubdocument.js +207 -0
  253. package/lib/types/buffer.js +294 -0
  254. package/lib/types/decimal128.js +13 -0
  255. package/lib/types/documentArray/index.js +113 -0
  256. package/lib/types/documentArray/isMongooseDocumentArray.js +5 -0
  257. package/lib/types/documentArray/methods/index.js +415 -0
  258. package/lib/types/double.js +13 -0
  259. package/lib/types/index.js +23 -0
  260. package/lib/types/map.js +419 -0
  261. package/lib/types/objectid.js +41 -0
  262. package/lib/types/subdocument.js +464 -0
  263. package/lib/types/uuid.js +13 -0
  264. package/lib/utils.js +1054 -0
  265. package/lib/validOptions.js +42 -0
  266. package/lib/virtualType.js +204 -0
  267. package/package.json +148 -0
  268. package/types/aggregate.d.ts +180 -0
  269. package/types/augmentations.d.ts +9 -0
  270. package/types/callback.d.ts +8 -0
  271. package/types/collection.d.ts +49 -0
  272. package/types/connection.d.ts +297 -0
  273. package/types/cursor.d.ts +67 -0
  274. package/types/document.d.ts +374 -0
  275. package/types/error.d.ts +143 -0
  276. package/types/expressions.d.ts +3053 -0
  277. package/types/helpers.d.ts +32 -0
  278. package/types/index.d.ts +1056 -0
  279. package/types/indexes.d.ts +97 -0
  280. package/types/inferhydrateddoctype.d.ts +115 -0
  281. package/types/inferrawdoctype.d.ts +135 -0
  282. package/types/inferschematype.d.ts +337 -0
  283. package/types/middlewares.d.ts +59 -0
  284. package/types/models.d.ts +1306 -0
  285. package/types/mongooseoptions.d.ts +228 -0
  286. package/types/pipelinestage.d.ts +333 -0
  287. package/types/populate.d.ts +53 -0
  288. package/types/query.d.ts +934 -0
  289. package/types/schemaoptions.d.ts +282 -0
  290. package/types/schematypes.d.ts +654 -0
  291. package/types/session.d.ts +32 -0
  292. package/types/types.d.ts +109 -0
  293. package/types/utility.d.ts +175 -0
  294. package/types/validation.d.ts +39 -0
  295. package/types/virtuals.d.ts +14 -0
@@ -0,0 +1,1189 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * Module dependencies
5
+ */
6
+
7
+ const AggregationCursor = require('./cursor/aggregationCursor');
8
+ const MongooseError = require('./error/mongooseError');
9
+ const Query = require('./query');
10
+ const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption');
11
+ const clone = require('./helpers/clone');
12
+ const getConstructorName = require('./helpers/getConstructorName');
13
+ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
14
+ const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
15
+ const utils = require('./utils');
16
+ const { modelSymbol } = require('./helpers/symbols');
17
+ const read = Query.prototype.read;
18
+ const readConcern = Query.prototype.readConcern;
19
+
20
+ const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']);
21
+
22
+ /**
23
+ * Aggregate constructor used for building aggregation pipelines. Do not
24
+ * instantiate this class directly, use [Model.aggregate()](https://mongoosejs.com/docs/api/model.html#Model.aggregate()) instead.
25
+ *
26
+ * #### Example:
27
+ *
28
+ * const aggregate = Model.aggregate([
29
+ * { $project: { a: 1, b: 1 } },
30
+ * { $skip: 5 }
31
+ * ]);
32
+ *
33
+ * Model.
34
+ * aggregate([{ $match: { age: { $gte: 21 }}}]).
35
+ * unwind('tags').
36
+ * exec();
37
+ *
38
+ * #### Note:
39
+ *
40
+ * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
41
+ * - Mongoose does **not** cast pipeline stages. The below will **not** work unless `_id` is a string in the database
42
+ *
43
+ * new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
44
+ * // Do this instead to cast to an ObjectId
45
+ * new Aggregate([{ $match: { _id: new mongoose.Types.ObjectId('00000000000000000000000a') } }]);
46
+ *
47
+ * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
48
+ * @see driver https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#aggregate
49
+ * @param {Array} [pipeline] aggregation pipeline as an array of objects
50
+ * @param {Model|Connection} [modelOrConn] the model or connection to use with this aggregate.
51
+ * @api public
52
+ */
53
+
54
+ function Aggregate(pipeline, modelOrConn) {
55
+ this._pipeline = [];
56
+ if (modelOrConn == null || modelOrConn[modelSymbol]) {
57
+ this._model = modelOrConn;
58
+ } else {
59
+ this._connection = modelOrConn;
60
+ }
61
+ this.options = {};
62
+
63
+ if (arguments.length === 1 && Array.isArray(pipeline)) {
64
+ this.append.apply(this, pipeline);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Contains options passed down to the [aggregate command](https://www.mongodb.com/docs/manual/reference/command/aggregate/).
70
+ * Supported options are:
71
+ *
72
+ * - [`allowDiskUse`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.allowDiskUse())
73
+ * - `bypassDocumentValidation`
74
+ * - [`collation`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.collation())
75
+ * - `comment`
76
+ * - [`cursor`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.cursor())
77
+ * - [`explain`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.explain())
78
+ * - `fieldsAsRaw`
79
+ * - [`hint`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.hint())
80
+ * - `let`
81
+ * - `maxTimeMS`
82
+ * - `raw`
83
+ * - [`readConcern`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.readConcern())
84
+ * - `readPreference`
85
+ * - [`session`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.session())
86
+ * - `writeConcern`
87
+ *
88
+ * @property options
89
+ * @memberOf Aggregate
90
+ * @api public
91
+ */
92
+
93
+ Aggregate.prototype.options;
94
+
95
+ /**
96
+ * Returns default options for this aggregate.
97
+ *
98
+ * @param {Model} model
99
+ * @api private
100
+ */
101
+
102
+ Aggregate.prototype._optionsForExec = function() {
103
+ const options = this.options || {};
104
+
105
+ const asyncLocalStorage = this.model()?.db?.base.transactionAsyncLocalStorage?.getStore();
106
+ if (!Object.hasOwn(options, 'session') && asyncLocalStorage?.session != null) {
107
+ options.session = asyncLocalStorage.session;
108
+ }
109
+
110
+ return options;
111
+ };
112
+
113
+ /**
114
+ * Get/set the model that this aggregation will execute on.
115
+ *
116
+ * #### Example:
117
+ *
118
+ * const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
119
+ * aggregate.model() === MyModel; // true
120
+ *
121
+ * // Change the model. There's rarely any reason to do this.
122
+ * aggregate.model(SomeOtherModel);
123
+ * aggregate.model() === SomeOtherModel; // true
124
+ *
125
+ * @param {Model} [model] Set the model associated with this aggregate. If not provided, returns the already stored model.
126
+ * @return {Model}
127
+ * @api public
128
+ */
129
+
130
+ Aggregate.prototype.model = function(model) {
131
+ if (arguments.length === 0) {
132
+ return this._model;
133
+ }
134
+
135
+ this._model = model;
136
+ if (model.schema != null) {
137
+ if (this.options.readPreference == null &&
138
+ model.schema.options.read != null) {
139
+ this.options.readPreference = model.schema.options.read;
140
+ }
141
+ if (this.options.collation == null &&
142
+ model.schema.options.collation != null) {
143
+ this.options.collation = model.schema.options.collation;
144
+ }
145
+ }
146
+
147
+ return model;
148
+ };
149
+
150
+ /**
151
+ * Appends new operators to this aggregate pipeline
152
+ *
153
+ * #### Example:
154
+ *
155
+ * aggregate.append({ $project: { field: 1 }}, { $limit: 2 });
156
+ *
157
+ * // or pass an array
158
+ * const pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
159
+ * aggregate.append(pipeline);
160
+ *
161
+ * @param {...Object|Object[]} ops operator(s) to append. Can either be a spread of objects or a single parameter of a object array.
162
+ * @return {Aggregate}
163
+ * @api public
164
+ */
165
+
166
+ Aggregate.prototype.append = function() {
167
+ const args = (arguments.length === 1 && Array.isArray(arguments[0]))
168
+ ? arguments[0]
169
+ : [...arguments];
170
+
171
+ if (!args.every(isOperator)) {
172
+ throw new Error('Arguments must be aggregate pipeline operators');
173
+ }
174
+
175
+ this._pipeline = this._pipeline.concat(args);
176
+
177
+ return this;
178
+ };
179
+
180
+ /**
181
+ * Appends a new $addFields operator to this aggregate pipeline.
182
+ * Requires MongoDB v3.4+ to work
183
+ *
184
+ * #### Example:
185
+ *
186
+ * // adding new fields based on existing fields
187
+ * aggregate.addFields({
188
+ * newField: '$b.nested'
189
+ * , plusTen: { $add: ['$val', 10]}
190
+ * , sub: {
191
+ * name: '$a'
192
+ * }
193
+ * })
194
+ *
195
+ * // etc
196
+ * aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });
197
+ *
198
+ * @param {Object} arg field specification
199
+ * @see $addFields https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/
200
+ * @return {Aggregate}
201
+ * @api public
202
+ */
203
+ Aggregate.prototype.addFields = function(arg) {
204
+ if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) {
205
+ throw new Error('Invalid addFields() argument. Must be an object');
206
+ }
207
+ return this.append({ $addFields: Object.assign({}, arg) });
208
+ };
209
+
210
+ /**
211
+ * Appends a new $project operator to this aggregate pipeline.
212
+ *
213
+ * Mongoose query [selection syntax](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) is also supported.
214
+ *
215
+ * #### Example:
216
+ *
217
+ * // include a, include b, exclude _id
218
+ * aggregate.project("a b -_id");
219
+ *
220
+ * // or you may use object notation, useful when
221
+ * // you have keys already prefixed with a "-"
222
+ * aggregate.project({a: 1, b: 1, _id: 0});
223
+ *
224
+ * // reshaping documents
225
+ * aggregate.project({
226
+ * newField: '$b.nested'
227
+ * , plusTen: { $add: ['$val', 10]}
228
+ * , sub: {
229
+ * name: '$a'
230
+ * }
231
+ * })
232
+ *
233
+ * // etc
234
+ * aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } });
235
+ *
236
+ * @param {Object|String} arg field specification
237
+ * @see projection https://www.mongodb.com/docs/manual/reference/aggregation/project/
238
+ * @return {Aggregate}
239
+ * @api public
240
+ */
241
+
242
+ Aggregate.prototype.project = function(arg) {
243
+ const fields = {};
244
+
245
+ if (typeof arg === 'object' && !Array.isArray(arg)) {
246
+ Object.keys(arg).forEach(function(field) {
247
+ fields[field] = arg[field];
248
+ });
249
+ } else if (arguments.length === 1 && typeof arg === 'string') {
250
+ arg.split(/\s+/).forEach(function(field) {
251
+ if (!field) {
252
+ return;
253
+ }
254
+ const include = field[0] === '-' ? 0 : 1;
255
+ if (include === 0) {
256
+ field = field.substring(1);
257
+ }
258
+ fields[field] = include;
259
+ });
260
+ } else {
261
+ throw new Error('Invalid project() argument. Must be string or object');
262
+ }
263
+
264
+ return this.append({ $project: fields });
265
+ };
266
+
267
+ /**
268
+ * Appends a new custom $group operator to this aggregate pipeline.
269
+ *
270
+ * #### Example:
271
+ *
272
+ * aggregate.group({ _id: "$department" });
273
+ *
274
+ * @see $group https://www.mongodb.com/docs/manual/reference/aggregation/group/
275
+ * @method group
276
+ * @memberOf Aggregate
277
+ * @instance
278
+ * @param {Object} arg $group operator contents
279
+ * @return {Aggregate}
280
+ * @api public
281
+ */
282
+
283
+ /**
284
+ * Appends a new custom $match operator to this aggregate pipeline.
285
+ *
286
+ * #### Example:
287
+ *
288
+ * aggregate.match({ department: { $in: [ "sales", "engineering" ] } });
289
+ *
290
+ * @see $match https://www.mongodb.com/docs/manual/reference/aggregation/match/
291
+ * @method match
292
+ * @memberOf Aggregate
293
+ * @instance
294
+ * @param {Object} arg $match operator contents
295
+ * @return {Aggregate}
296
+ * @api public
297
+ */
298
+
299
+ /**
300
+ * Appends a new $skip operator to this aggregate pipeline.
301
+ *
302
+ * #### Example:
303
+ *
304
+ * aggregate.skip(10);
305
+ *
306
+ * @see $skip https://www.mongodb.com/docs/manual/reference/aggregation/skip/
307
+ * @method skip
308
+ * @memberOf Aggregate
309
+ * @instance
310
+ * @param {Number} num number of records to skip before next stage
311
+ * @return {Aggregate}
312
+ * @api public
313
+ */
314
+
315
+ /**
316
+ * Appends a new $limit operator to this aggregate pipeline.
317
+ *
318
+ * #### Example:
319
+ *
320
+ * aggregate.limit(10);
321
+ *
322
+ * @see $limit https://www.mongodb.com/docs/manual/reference/aggregation/limit/
323
+ * @method limit
324
+ * @memberOf Aggregate
325
+ * @instance
326
+ * @param {Number} num maximum number of records to pass to the next stage
327
+ * @return {Aggregate}
328
+ * @api public
329
+ */
330
+
331
+
332
+ /**
333
+ * Appends a new $densify operator to this aggregate pipeline.
334
+ *
335
+ * #### Example:
336
+ *
337
+ * aggregate.densify({
338
+ * field: 'timestamp',
339
+ * range: {
340
+ * step: 1,
341
+ * unit: 'hour',
342
+ * bounds: [new Date('2021-05-18T00:00:00.000Z'), new Date('2021-05-18T08:00:00.000Z')]
343
+ * }
344
+ * });
345
+ *
346
+ * @see $densify https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/
347
+ * @method densify
348
+ * @memberOf Aggregate
349
+ * @instance
350
+ * @param {Object} arg $densify operator contents
351
+ * @return {Aggregate}
352
+ * @api public
353
+ */
354
+
355
+ /**
356
+ * Appends a new $fill operator to this aggregate pipeline.
357
+ *
358
+ * #### Example:
359
+ *
360
+ * aggregate.fill({
361
+ * output: {
362
+ * bootsSold: { value: 0 },
363
+ * sandalsSold: { value: 0 },
364
+ * sneakersSold: { value: 0 }
365
+ * }
366
+ * });
367
+ *
368
+ * @see $fill https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
369
+ * @method fill
370
+ * @memberOf Aggregate
371
+ * @instance
372
+ * @param {Object} arg $fill operator contents
373
+ * @return {Aggregate}
374
+ * @api public
375
+ */
376
+
377
+ /**
378
+ * Appends a new $geoNear operator to this aggregate pipeline.
379
+ *
380
+ * #### Note:
381
+ *
382
+ * **MUST** be used as the first operator in the pipeline.
383
+ *
384
+ * #### Example:
385
+ *
386
+ * aggregate.near({
387
+ * near: { type: 'Point', coordinates: [40.724, -73.997] },
388
+ * distanceField: "dist.calculated", // required
389
+ * maxDistance: 0.008,
390
+ * query: { type: "public" },
391
+ * includeLocs: "dist.location",
392
+ * spherical: true,
393
+ * });
394
+ *
395
+ * @see $geoNear https://www.mongodb.com/docs/manual/reference/aggregation/geoNear/
396
+ * @method near
397
+ * @memberOf Aggregate
398
+ * @instance
399
+ * @param {Object} arg
400
+ * @param {Object|Array<Number>} arg.near GeoJSON point or coordinates array
401
+ * @return {Aggregate}
402
+ * @api public
403
+ */
404
+
405
+ Aggregate.prototype.near = function(arg) {
406
+ if (arg == null) {
407
+ throw new MongooseError('Aggregate `near()` must be called with non-nullish argument');
408
+ }
409
+ if (arg.near == null) {
410
+ throw new MongooseError('Aggregate `near()` argument must have a `near` property');
411
+ }
412
+ const coordinates = Array.isArray(arg.near) ? arg.near : arg.near.coordinates;
413
+ if (typeof arg.near === 'object' && (!Array.isArray(coordinates) || coordinates.length < 2 || coordinates.find(c => typeof c !== 'number'))) {
414
+ throw new MongooseError(`Aggregate \`near()\` argument has invalid coordinates, got "${coordinates}"`);
415
+ }
416
+
417
+ const op = {};
418
+ op.$geoNear = arg;
419
+ return this.append(op);
420
+ };
421
+
422
+ /*!
423
+ * define methods
424
+ */
425
+
426
+ 'group match skip limit out densify fill'.split(' ').forEach(function($operator) {
427
+ Aggregate.prototype[$operator] = function(arg) {
428
+ const op = {};
429
+ op['$' + $operator] = arg;
430
+ return this.append(op);
431
+ };
432
+ });
433
+
434
+ /**
435
+ * Appends new custom $unwind operator(s) to this aggregate pipeline.
436
+ *
437
+ * Note that the `$unwind` operator requires the path name to start with '$'.
438
+ * Mongoose will prepend '$' if the specified field doesn't start '$'.
439
+ *
440
+ * #### Example:
441
+ *
442
+ * aggregate.unwind("tags");
443
+ * aggregate.unwind("a", "b", "c");
444
+ * aggregate.unwind({ path: '$tags', preserveNullAndEmptyArrays: true });
445
+ *
446
+ * @see $unwind https://www.mongodb.com/docs/manual/reference/aggregation/unwind/
447
+ * @param {String|Object|String[]|Object[]} fields the field(s) to unwind, either as field names or as [objects with options](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#document-operand-with-options). If passing a string, prefixing the field name with '$' is optional. If passing an object, `path` must start with '$'.
448
+ * @return {Aggregate}
449
+ * @api public
450
+ */
451
+
452
+ Aggregate.prototype.unwind = function() {
453
+ const args = [...arguments];
454
+
455
+ const res = [];
456
+ for (const arg of args) {
457
+ if (arg && typeof arg === 'object') {
458
+ res.push({ $unwind: arg });
459
+ } else if (typeof arg === 'string') {
460
+ res.push({
461
+ $unwind: (arg[0] === '$') ? arg : '$' + arg
462
+ });
463
+ } else {
464
+ throw new Error('Invalid arg "' + arg + '" to unwind(), ' +
465
+ 'must be string or object');
466
+ }
467
+ }
468
+
469
+ return this.append.apply(this, res);
470
+ };
471
+
472
+ /**
473
+ * Appends a new $replaceRoot operator to this aggregate pipeline.
474
+ *
475
+ * Note that the `$replaceRoot` operator requires field strings to start with '$'.
476
+ * If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'.
477
+ * If you are passing in an object the strings in your expression will not be altered.
478
+ *
479
+ * #### Example:
480
+ *
481
+ * aggregate.replaceRoot("user");
482
+ *
483
+ * aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } });
484
+ *
485
+ * @see $replaceRoot https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot
486
+ * @param {String|Object} newRoot the field or document which will become the new root document
487
+ * @return {Aggregate}
488
+ * @api public
489
+ */
490
+
491
+ Aggregate.prototype.replaceRoot = function(newRoot) {
492
+ let ret;
493
+
494
+ if (typeof newRoot === 'string') {
495
+ ret = newRoot.startsWith('$') ? newRoot : '$' + newRoot;
496
+ } else {
497
+ ret = newRoot;
498
+ }
499
+
500
+ return this.append({
501
+ $replaceRoot: {
502
+ newRoot: ret
503
+ }
504
+ });
505
+ };
506
+
507
+ /**
508
+ * Appends a new $count operator to this aggregate pipeline.
509
+ *
510
+ * #### Example:
511
+ *
512
+ * aggregate.count("userCount");
513
+ *
514
+ * @see $count https://www.mongodb.com/docs/manual/reference/operator/aggregation/count
515
+ * @param {String} fieldName The name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character.
516
+ * @return {Aggregate}
517
+ * @api public
518
+ */
519
+
520
+ Aggregate.prototype.count = function(fieldName) {
521
+ return this.append({ $count: fieldName });
522
+ };
523
+
524
+ /**
525
+ * Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name
526
+ * or a pipeline object.
527
+ *
528
+ * Note that the `$sortByCount` operator requires the new root to start with '$'.
529
+ * Mongoose will prepend '$' if the specified field name doesn't start with '$'.
530
+ *
531
+ * #### Example:
532
+ *
533
+ * aggregate.sortByCount('users');
534
+ * aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] })
535
+ *
536
+ * @see $sortByCount https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/
537
+ * @param {Object|String} arg
538
+ * @return {Aggregate} this
539
+ * @api public
540
+ */
541
+
542
+ Aggregate.prototype.sortByCount = function(arg) {
543
+ if (arg && typeof arg === 'object') {
544
+ return this.append({ $sortByCount: arg });
545
+ } else if (typeof arg === 'string') {
546
+ return this.append({
547
+ $sortByCount: (arg[0] === '$') ? arg : '$' + arg
548
+ });
549
+ } else {
550
+ throw new TypeError('Invalid arg "' + arg + '" to sortByCount(), ' +
551
+ 'must be string or object');
552
+ }
553
+ };
554
+
555
+ /**
556
+ * Appends new custom $lookup operator to this aggregate pipeline.
557
+ *
558
+ * #### Example:
559
+ *
560
+ * aggregate.lookup({ from: 'users', localField: 'userId', foreignField: '_id', as: 'users' });
561
+ *
562
+ * @see $lookup https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#pipe._S_lookup
563
+ * @param {Object} options to $lookup as described in the above link
564
+ * @return {Aggregate}
565
+ * @api public
566
+ */
567
+
568
+ Aggregate.prototype.lookup = function(options) {
569
+ return this.append({ $lookup: options });
570
+ };
571
+
572
+ /**
573
+ * Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection.
574
+ *
575
+ * Note that graphLookup can only consume at most 100MB of memory, and does not allow disk use even if `{ allowDiskUse: true }` is specified.
576
+ *
577
+ * #### Example:
578
+ *
579
+ * // Suppose we have a collection of courses, where a document might look like `{ _id: 0, name: 'Calculus', prerequisite: 'Trigonometry'}` and `{ _id: 0, name: 'Trigonometry', prerequisite: 'Algebra' }`
580
+ * aggregate.graphLookup({ from: 'courses', startWith: '$prerequisite', connectFromField: 'prerequisite', connectToField: 'name', as: 'prerequisites', maxDepth: 3 }) // this will recursively search the 'courses' collection up to 3 prerequisites
581
+ *
582
+ * @see $graphLookup https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#pipe._S_graphLookup
583
+ * @param {Object} options to $graphLookup as described in the above link
584
+ * @return {Aggregate}
585
+ * @api public
586
+ */
587
+
588
+ Aggregate.prototype.graphLookup = function(options) {
589
+ const cloneOptions = {};
590
+ if (options) {
591
+ if (!utils.isObject(options)) {
592
+ throw new TypeError('Invalid graphLookup() argument. Must be an object.');
593
+ }
594
+
595
+ utils.mergeClone(cloneOptions, options);
596
+ const startWith = cloneOptions.startWith;
597
+
598
+ if (startWith && typeof startWith === 'string') {
599
+ cloneOptions.startWith = cloneOptions.startWith.startsWith('$') ?
600
+ cloneOptions.startWith :
601
+ '$' + cloneOptions.startWith;
602
+ }
603
+
604
+ }
605
+ return this.append({ $graphLookup: cloneOptions });
606
+ };
607
+
608
+ /**
609
+ * Appends new custom $sample operator to this aggregate pipeline.
610
+ *
611
+ * #### Example:
612
+ *
613
+ * aggregate.sample(3); // Add a pipeline that picks 3 random documents
614
+ *
615
+ * @see $sample https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#pipe._S_sample
616
+ * @param {Number} size number of random documents to pick
617
+ * @return {Aggregate}
618
+ * @api public
619
+ */
620
+
621
+ Aggregate.prototype.sample = function(size) {
622
+ return this.append({ $sample: { size: size } });
623
+ };
624
+
625
+ /**
626
+ * Appends a new $sort operator to this aggregate pipeline.
627
+ *
628
+ * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`.
629
+ *
630
+ * If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending.
631
+ *
632
+ * #### Example:
633
+ *
634
+ * // these are equivalent
635
+ * aggregate.sort({ field: 'asc', test: -1 });
636
+ * aggregate.sort('field -test');
637
+ *
638
+ * @see $sort https://www.mongodb.com/docs/manual/reference/aggregation/sort/
639
+ * @param {Object|String} arg
640
+ * @return {Aggregate} this
641
+ * @api public
642
+ */
643
+
644
+ Aggregate.prototype.sort = function(arg) {
645
+ // TODO refactor to reuse the query builder logic
646
+
647
+ const sort = {};
648
+
649
+ if (getConstructorName(arg) === 'Object') {
650
+ const desc = ['desc', 'descending', -1];
651
+ Object.keys(arg).forEach(function(field) {
652
+ // If sorting by text score, skip coercing into 1/-1
653
+ if (arg[field] instanceof Object && arg[field].$meta) {
654
+ sort[field] = arg[field];
655
+ return;
656
+ }
657
+ sort[field] = desc.indexOf(arg[field]) === -1 ? 1 : -1;
658
+ });
659
+ } else if (arguments.length === 1 && typeof arg === 'string') {
660
+ arg.split(/\s+/).forEach(function(field) {
661
+ if (!field) {
662
+ return;
663
+ }
664
+ const ascend = field[0] === '-' ? -1 : 1;
665
+ if (ascend === -1) {
666
+ field = field.substring(1);
667
+ }
668
+ sort[field] = ascend;
669
+ });
670
+ } else {
671
+ throw new TypeError('Invalid sort() argument. Must be a string or object.');
672
+ }
673
+
674
+ return this.append({ $sort: sort });
675
+ };
676
+
677
+ /**
678
+ * Appends new $unionWith operator to this aggregate pipeline.
679
+ *
680
+ * #### Example:
681
+ *
682
+ * aggregate.unionWith({ coll: 'users', pipeline: [ { $match: { _id: 1 } } ] });
683
+ *
684
+ * @see $unionWith https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith
685
+ * @param {Object} options to $unionWith query as described in the above link
686
+ * @return {Aggregate}
687
+ * @api public
688
+ */
689
+
690
+ Aggregate.prototype.unionWith = function(options) {
691
+ return this.append({ $unionWith: options });
692
+ };
693
+
694
+
695
+ /**
696
+ * Sets the readPreference option for the aggregation query.
697
+ *
698
+ * #### Example:
699
+ *
700
+ * await Model.aggregate(pipeline).read('primaryPreferred');
701
+ *
702
+ * @param {String|ReadPreference} pref one of the listed preference options or their aliases
703
+ * @param {Array} [tags] optional tags for this query.
704
+ * @return {Aggregate} this
705
+ * @api public
706
+ * @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference
707
+ */
708
+
709
+ Aggregate.prototype.read = function(pref, tags) {
710
+ read.call(this, pref, tags);
711
+ return this;
712
+ };
713
+
714
+ /**
715
+ * Sets the readConcern level for the aggregation query.
716
+ *
717
+ * #### Example:
718
+ *
719
+ * await Model.aggregate(pipeline).readConcern('majority');
720
+ *
721
+ * @param {String} level one of the listed read concern level or their aliases
722
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/read-concern/
723
+ * @return {Aggregate} this
724
+ * @api public
725
+ */
726
+
727
+ Aggregate.prototype.readConcern = function(level) {
728
+ readConcern.call(this, level);
729
+ return this;
730
+ };
731
+
732
+ /**
733
+ * Appends a new $redact operator to this aggregate pipeline.
734
+ *
735
+ * If 3 arguments are supplied, Mongoose will wrap them with if-then-else of $cond operator respectively
736
+ * If `thenExpr` or `elseExpr` is string, make sure it starts with $$, like `$$DESCEND`, `$$PRUNE` or `$$KEEP`.
737
+ *
738
+ * #### Example:
739
+ *
740
+ * await Model.aggregate(pipeline).redact({
741
+ * $cond: {
742
+ * if: { $eq: [ '$level', 5 ] },
743
+ * then: '$$PRUNE',
744
+ * else: '$$DESCEND'
745
+ * }
746
+ * });
747
+ *
748
+ * // $redact often comes with $cond operator, you can also use the following syntax provided by mongoose
749
+ * await Model.aggregate(pipeline).redact({ $eq: [ '$level', 5 ] }, '$$PRUNE', '$$DESCEND');
750
+ *
751
+ * @param {Object} expression redact options or conditional expression
752
+ * @param {String|Object} [thenExpr] true case for the condition
753
+ * @param {String|Object} [elseExpr] false case for the condition
754
+ * @return {Aggregate} this
755
+ * @see $redact https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/
756
+ * @api public
757
+ */
758
+
759
+ Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
760
+ if (arguments.length === 3) {
761
+ if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||
762
+ (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
763
+ throw new Error('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');
764
+ }
765
+
766
+ expression = {
767
+ $cond: {
768
+ if: expression,
769
+ then: thenExpr,
770
+ else: elseExpr
771
+ }
772
+ };
773
+ } else if (arguments.length !== 1) {
774
+ throw new TypeError('Invalid arguments');
775
+ }
776
+
777
+ return this.append({ $redact: expression });
778
+ };
779
+
780
+ /**
781
+ * Execute the aggregation with explain
782
+ *
783
+ * #### Example:
784
+ *
785
+ * Model.aggregate(..).explain()
786
+ *
787
+ * @param {String} [verbosity]
788
+ * @return {Promise}
789
+ */
790
+
791
+ Aggregate.prototype.explain = async function explain(verbosity) {
792
+ if (typeof verbosity === 'function' || typeof arguments[1] === 'function') {
793
+ throw new MongooseError('Aggregate.prototype.explain() no longer accepts a callback');
794
+ }
795
+ const model = this._model;
796
+
797
+ if (!this._pipeline.length) {
798
+ throw new Error('Aggregate has empty pipeline');
799
+ }
800
+
801
+ prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
802
+
803
+ try {
804
+ await model.hooks.execPre('aggregate', this);
805
+ } catch (error) {
806
+ return await model.hooks.execPost('aggregate', this, [null], { error });
807
+ }
808
+
809
+ const cursor = model.collection.aggregate(this._pipeline, this.options);
810
+
811
+ if (verbosity == null) {
812
+ verbosity = true;
813
+ }
814
+
815
+ let result = null;
816
+ try {
817
+ result = await cursor.explain(verbosity);
818
+ } catch (error) {
819
+ return await model.hooks.execPost('aggregate', this, [null], { error });
820
+ }
821
+
822
+ await model.hooks.execPost('aggregate', this, [result], { error: null });
823
+
824
+ return result;
825
+ };
826
+
827
+ /**
828
+ * Sets the allowDiskUse option for the aggregation query
829
+ *
830
+ * #### Example:
831
+ *
832
+ * await Model.aggregate([{ $match: { foo: 'bar' } }]).allowDiskUse(true);
833
+ *
834
+ * @param {Boolean} value Should tell server it can use hard drive to store data during aggregation.
835
+ * @return {Aggregate} this
836
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
837
+ */
838
+
839
+ Aggregate.prototype.allowDiskUse = function(value) {
840
+ this.options.allowDiskUse = value;
841
+ return this;
842
+ };
843
+
844
+ /**
845
+ * Sets the hint option for the aggregation query
846
+ *
847
+ * #### Example:
848
+ *
849
+ * Model.aggregate(..).hint({ qty: 1, category: 1 }).exec();
850
+ *
851
+ * @param {Object|String} value a hint object or the index name
852
+ * @return {Aggregate} this
853
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
854
+ */
855
+
856
+ Aggregate.prototype.hint = function(value) {
857
+ this.options.hint = value;
858
+ return this;
859
+ };
860
+
861
+ /**
862
+ * Sets the session for this aggregation. Useful for [transactions](https://mongoosejs.com/docs/transactions.html).
863
+ *
864
+ * #### Example:
865
+ *
866
+ * const session = await Model.startSession();
867
+ * await Model.aggregate(..).session(session);
868
+ *
869
+ * @param {ClientSession} session
870
+ * @return {Aggregate} this
871
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
872
+ */
873
+
874
+ Aggregate.prototype.session = function(session) {
875
+ if (session == null) {
876
+ delete this.options.session;
877
+ } else {
878
+ this.options.session = session;
879
+ }
880
+ return this;
881
+ };
882
+
883
+ /**
884
+ * Lets you set arbitrary options, for middleware or plugins.
885
+ *
886
+ * #### Example:
887
+ *
888
+ * const agg = Model.aggregate(..).option({ allowDiskUse: true }); // Set the `allowDiskUse` option
889
+ * agg.options; // `{ allowDiskUse: true }`
890
+ *
891
+ * @param {Object} options keys to merge into current options
892
+ * @param {Number} [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/)
893
+ * @param {Boolean} [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation
894
+ * @param {Object} [options.collation] object see [`Aggregate.prototype.collation()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.collation())
895
+ * @param {ClientSession} [options.session] ClientSession see [`Aggregate.prototype.session()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.session())
896
+ * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
897
+ * @return {Aggregate} this
898
+ * @api public
899
+ */
900
+
901
+ Aggregate.prototype.option = function(value) {
902
+ for (const key in value) {
903
+ this.options[key] = value[key];
904
+ }
905
+ return this;
906
+ };
907
+
908
+ /**
909
+ * Sets the `cursor` option and executes this aggregation, returning an aggregation cursor.
910
+ * Cursors are useful if you want to process the results of the aggregation one-at-a-time
911
+ * because the aggregation result is too big to fit into memory.
912
+ *
913
+ * #### Example:
914
+ *
915
+ * const cursor = Model.aggregate(..).cursor({ batchSize: 1000 });
916
+ * cursor.eachAsync(function(doc, i) {
917
+ * // use doc
918
+ * });
919
+ *
920
+ * @param {Object} options
921
+ * @param {Number} [options.batchSize] set the cursor batch size
922
+ * @param {Boolean} [options.useMongooseAggCursor] use experimental mongoose-specific aggregation cursor (for `eachAsync()` and other query cursor semantics)
923
+ * @return {AggregationCursor} cursor representing this aggregation
924
+ * @api public
925
+ * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html
926
+ */
927
+
928
+ Aggregate.prototype.cursor = function(options) {
929
+ this._optionsForExec();
930
+ this.options.cursor = options || {};
931
+ return new AggregationCursor(this); // return this;
932
+ };
933
+
934
+ /**
935
+ * Adds a collation
936
+ *
937
+ * #### Example:
938
+ *
939
+ * const res = await Model.aggregate(pipeline).collation({ locale: 'en_US', strength: 1 });
940
+ *
941
+ * @param {Object} collation options
942
+ * @return {Aggregate} this
943
+ * @api public
944
+ * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/interfaces/CollationOptions.html
945
+ */
946
+
947
+ Aggregate.prototype.collation = function(collation) {
948
+ this.options.collation = collation;
949
+ return this;
950
+ };
951
+
952
+ /**
953
+ * Combines multiple aggregation pipelines.
954
+ *
955
+ * #### Example:
956
+ *
957
+ * const res = await Model.aggregate().facet({
958
+ * books: [{ groupBy: '$author' }],
959
+ * price: [{ $bucketAuto: { groupBy: '$price', buckets: 2 } }]
960
+ * });
961
+ *
962
+ * // Output: { books: [...], price: [{...}, {...}] }
963
+ *
964
+ * @param {Object} facet options
965
+ * @return {Aggregate} this
966
+ * @see $facet https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/
967
+ * @api public
968
+ */
969
+
970
+ Aggregate.prototype.facet = function(options) {
971
+ return this.append({ $facet: options });
972
+ };
973
+
974
+ /**
975
+ * Helper for [Atlas Text Search](https://www.mongodb.com/docs/atlas/atlas-search/tutorial/)'s
976
+ * `$search` stage.
977
+ *
978
+ * #### Example:
979
+ *
980
+ * const res = await Model.aggregate().
981
+ * search({
982
+ * text: {
983
+ * query: 'baseball',
984
+ * path: 'plot'
985
+ * }
986
+ * });
987
+ *
988
+ * // Output: [{ plot: '...', title: '...' }]
989
+ *
990
+ * @param {Object} $search options
991
+ * @return {Aggregate} this
992
+ * @see $search https://www.mongodb.com/docs/atlas/atlas-search/tutorial/
993
+ * @api public
994
+ */
995
+
996
+ Aggregate.prototype.search = function(options) {
997
+ return this.append({ $search: options });
998
+ };
999
+
1000
+ /**
1001
+ * Returns the current pipeline
1002
+ *
1003
+ * #### Example:
1004
+ *
1005
+ * MyModel.aggregate().match({ test: 1 }).pipeline(); // [{ $match: { test: 1 } }]
1006
+ *
1007
+ * @return {Array} The current pipeline similar to the operation that will be executed
1008
+ * @api public
1009
+ */
1010
+
1011
+ Aggregate.prototype.pipeline = function() {
1012
+ return this._pipeline;
1013
+ };
1014
+
1015
+ /**
1016
+ * Executes the aggregate pipeline on the currently bound Model.
1017
+ *
1018
+ * #### Example:
1019
+ * const result = await aggregate.exec();
1020
+ *
1021
+ * @return {Promise}
1022
+ * @api public
1023
+ */
1024
+
1025
+ Aggregate.prototype.exec = async function exec() {
1026
+ if (!this._model && !this._connection) {
1027
+ throw new Error('Aggregate not bound to any Model');
1028
+ }
1029
+ if (typeof arguments[0] === 'function') {
1030
+ throw new MongooseError('Aggregate.prototype.exec() no longer accepts a callback');
1031
+ }
1032
+
1033
+ if (this._connection) {
1034
+ if (!this._pipeline.length) {
1035
+ throw new MongooseError('Aggregate has empty pipeline');
1036
+ }
1037
+
1038
+ this._optionsForExec();
1039
+
1040
+ const cursor = await this._connection.client.db().aggregate(this._pipeline, this.options);
1041
+ return await cursor.toArray();
1042
+ }
1043
+
1044
+ const model = this._model;
1045
+ const collection = this._model.collection;
1046
+
1047
+ applyGlobalMaxTimeMS(this.options, model.db.options, model.base.options);
1048
+ applyGlobalDiskUse(this.options, model.db.options, model.base.options);
1049
+
1050
+ this._optionsForExec();
1051
+
1052
+ if (this.options?.cursor) {
1053
+ return new AggregationCursor(this);
1054
+ }
1055
+
1056
+ prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
1057
+ stringifyFunctionOperators(this._pipeline);
1058
+
1059
+ try {
1060
+ await model.hooks.execPre('aggregate', this);
1061
+ } catch (error) {
1062
+ return await model.hooks.execPost('aggregate', this, [null], { error });
1063
+ }
1064
+
1065
+ if (!this._pipeline.length) {
1066
+ throw new MongooseError('Aggregate has empty pipeline');
1067
+ }
1068
+
1069
+ const options = clone(this.options || {});
1070
+
1071
+ let result;
1072
+ try {
1073
+ const cursor = await collection.aggregate(this._pipeline, options);
1074
+ result = await cursor.toArray();
1075
+ } catch (error) {
1076
+ return await model.hooks.execPost('aggregate', this, [null], { error });
1077
+ }
1078
+
1079
+ await model.hooks.execPost('aggregate', this, [result], { error: null });
1080
+
1081
+ return result;
1082
+ };
1083
+
1084
+ /**
1085
+ * Provides a Promise-like `then` function, which will call `.exec` without a callback
1086
+ * Compatible with `await`.
1087
+ *
1088
+ * #### Example:
1089
+ *
1090
+ * Model.aggregate(..).then(successCallback, errorCallback);
1091
+ *
1092
+ * @param {Function} [resolve] successCallback
1093
+ * @param {Function} [reject] errorCallback
1094
+ * @return {Promise}
1095
+ */
1096
+ Aggregate.prototype.then = function(resolve, reject) {
1097
+ return this.exec().then(resolve, reject);
1098
+ };
1099
+
1100
+ /**
1101
+ * Executes the aggregation returning a `Promise` which will be
1102
+ * resolved with either the doc(s) or rejected with the error.
1103
+ * Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then), but only takes a rejection handler.
1104
+ * Compatible with `await`.
1105
+ *
1106
+ * @param {Function} [reject]
1107
+ * @return {Promise}
1108
+ * @api public
1109
+ */
1110
+
1111
+ Aggregate.prototype.catch = function(reject) {
1112
+ return this.exec().then(null, reject);
1113
+ };
1114
+
1115
+ /**
1116
+ * Executes the aggregate returning a `Promise` which will be
1117
+ * resolved with `.finally()` chained.
1118
+ *
1119
+ * More about [Promise `finally()` in JavaScript](https://thecodebarbarian.com/using-promise-finally-in-node-js.html).
1120
+ *
1121
+ * @param {Function} [onFinally]
1122
+ * @return {Promise}
1123
+ * @api public
1124
+ */
1125
+
1126
+ Aggregate.prototype.finally = function(onFinally) {
1127
+ return this.exec().finally(onFinally);
1128
+ };
1129
+
1130
+ /**
1131
+ * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
1132
+ * You do not need to call this function explicitly, the JavaScript runtime
1133
+ * will call it for you.
1134
+ *
1135
+ * #### Example:
1136
+ *
1137
+ * const agg = Model.aggregate([{ $match: { age: { $gte: 25 } } }]);
1138
+ * for await (const doc of agg) {
1139
+ * console.log(doc.name);
1140
+ * }
1141
+ *
1142
+ * @method [Symbol.asyncIterator]
1143
+ * @memberOf Aggregate
1144
+ * @instance
1145
+ * @api public
1146
+ */
1147
+
1148
+ Aggregate.prototype[Symbol.asyncIterator] = function() {
1149
+ return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator();
1150
+ };
1151
+
1152
+ /*!
1153
+ * Helpers
1154
+ */
1155
+
1156
+ /**
1157
+ * Checks whether an object is likely a pipeline operator
1158
+ *
1159
+ * @param {Object} obj object to check
1160
+ * @return {Boolean}
1161
+ * @api private
1162
+ */
1163
+
1164
+ function isOperator(obj) {
1165
+ if (typeof obj !== 'object' || obj === null) {
1166
+ return false;
1167
+ }
1168
+
1169
+ const k = Object.keys(obj);
1170
+
1171
+ return k.length === 1 && k[0][0] === '$';
1172
+ }
1173
+
1174
+ /**
1175
+ * Adds the appropriate `$match` pipeline step to the top of an aggregate's
1176
+ * pipeline, should it's model is a non-root discriminator type. This is
1177
+ * analogous to the `prepareDiscriminatorCriteria` function in `lib/query.js`.
1178
+ *
1179
+ * @param {Aggregate} aggregate Aggregate to prepare
1180
+ * @api private
1181
+ */
1182
+
1183
+ Aggregate._prepareDiscriminatorPipeline = prepareDiscriminatorPipeline;
1184
+
1185
+ /*!
1186
+ * Exports
1187
+ */
1188
+
1189
+ module.exports = Aggregate;