@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.
- package/LICENSE.md +22 -0
- package/README.md +397 -0
- package/SECURITY.md +1 -0
- package/eslint.config.mjs +198 -0
- package/index.js +64 -0
- package/lib/aggregate.js +1189 -0
- package/lib/cast/bigint.js +46 -0
- package/lib/cast/boolean.js +32 -0
- package/lib/cast/date.js +41 -0
- package/lib/cast/decimal128.js +39 -0
- package/lib/cast/double.js +50 -0
- package/lib/cast/int32.js +36 -0
- package/lib/cast/number.js +42 -0
- package/lib/cast/objectid.js +29 -0
- package/lib/cast/string.js +37 -0
- package/lib/cast/uuid.js +35 -0
- package/lib/cast.js +436 -0
- package/lib/collection.js +321 -0
- package/lib/connection.js +1855 -0
- package/lib/connectionState.js +26 -0
- package/lib/constants.js +73 -0
- package/lib/cursor/aggregationCursor.js +466 -0
- package/lib/cursor/changeStream.js +198 -0
- package/lib/cursor/queryCursor.js +622 -0
- package/lib/document.js +5521 -0
- package/lib/driver.js +15 -0
- package/lib/drivers/SPEC.md +4 -0
- package/lib/drivers/node-mongodb-native/bulkWriteResult.js +5 -0
- package/lib/drivers/node-mongodb-native/collection.js +393 -0
- package/lib/drivers/node-mongodb-native/connection.js +506 -0
- package/lib/drivers/node-mongodb-native/index.js +10 -0
- package/lib/error/browserMissingSchema.js +29 -0
- package/lib/error/bulkSaveIncompleteError.js +44 -0
- package/lib/error/bulkWriteError.js +41 -0
- package/lib/error/cast.js +158 -0
- package/lib/error/createCollectionsError.js +26 -0
- package/lib/error/divergentArray.js +40 -0
- package/lib/error/eachAsyncMultiError.js +41 -0
- package/lib/error/index.js +237 -0
- package/lib/error/invalidSchemaOption.js +32 -0
- package/lib/error/messages.js +47 -0
- package/lib/error/missingSchema.js +33 -0
- package/lib/error/mongooseError.js +13 -0
- package/lib/error/notFound.js +47 -0
- package/lib/error/objectExpected.js +31 -0
- package/lib/error/objectParameter.js +31 -0
- package/lib/error/overwriteModel.js +31 -0
- package/lib/error/parallelSave.js +33 -0
- package/lib/error/parallelValidate.js +33 -0
- package/lib/error/serverSelection.js +62 -0
- package/lib/error/setOptionError.js +103 -0
- package/lib/error/strict.js +35 -0
- package/lib/error/strictPopulate.js +31 -0
- package/lib/error/syncIndexes.js +30 -0
- package/lib/error/validation.js +97 -0
- package/lib/error/validator.js +100 -0
- package/lib/error/version.js +38 -0
- package/lib/helpers/aggregate/prepareDiscriminatorPipeline.js +39 -0
- package/lib/helpers/aggregate/stringifyFunctionOperators.js +50 -0
- package/lib/helpers/arrayDepth.js +33 -0
- package/lib/helpers/clone.js +204 -0
- package/lib/helpers/common.js +127 -0
- package/lib/helpers/createJSONSchemaTypeDefinition.js +24 -0
- package/lib/helpers/cursor/eachAsync.js +225 -0
- package/lib/helpers/discriminator/applyEmbeddedDiscriminators.js +36 -0
- package/lib/helpers/discriminator/areDiscriminatorValuesEqual.js +16 -0
- package/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js +12 -0
- package/lib/helpers/discriminator/getConstructor.js +29 -0
- package/lib/helpers/discriminator/getDiscriminatorByValue.js +28 -0
- package/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js +27 -0
- package/lib/helpers/discriminator/mergeDiscriminatorSchema.js +91 -0
- package/lib/helpers/document/applyDefaults.js +132 -0
- package/lib/helpers/document/applyTimestamps.js +106 -0
- package/lib/helpers/document/applyVirtuals.js +147 -0
- package/lib/helpers/document/cleanModifiedSubpaths.js +45 -0
- package/lib/helpers/document/compile.js +238 -0
- package/lib/helpers/document/getDeepestSubdocumentForPath.js +38 -0
- package/lib/helpers/document/getEmbeddedDiscriminatorPath.js +53 -0
- package/lib/helpers/document/handleSpreadDoc.js +35 -0
- package/lib/helpers/each.js +25 -0
- package/lib/helpers/error/combinePathErrors.js +22 -0
- package/lib/helpers/firstKey.js +8 -0
- package/lib/helpers/get.js +65 -0
- package/lib/helpers/getConstructorName.js +16 -0
- package/lib/helpers/getDefaultBulkwriteResult.js +18 -0
- package/lib/helpers/getFunctionName.js +10 -0
- package/lib/helpers/immediate.js +16 -0
- package/lib/helpers/indexes/applySchemaCollation.js +13 -0
- package/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js +14 -0
- package/lib/helpers/indexes/getRelatedIndexes.js +63 -0
- package/lib/helpers/indexes/isDefaultIdIndex.js +18 -0
- package/lib/helpers/indexes/isIndexEqual.js +95 -0
- package/lib/helpers/indexes/isIndexSpecEqual.js +32 -0
- package/lib/helpers/indexes/isTextIndex.js +16 -0
- package/lib/helpers/indexes/isTimeseriesIndex.js +16 -0
- package/lib/helpers/isAsyncFunction.js +9 -0
- package/lib/helpers/isBsonType.js +15 -0
- package/lib/helpers/isMongooseObject.js +22 -0
- package/lib/helpers/isObject.js +16 -0
- package/lib/helpers/isPOJO.js +12 -0
- package/lib/helpers/isPromise.js +6 -0
- package/lib/helpers/isSimpleValidator.js +22 -0
- package/lib/helpers/minimize.js +41 -0
- package/lib/helpers/model/applyDefaultsToPOJO.js +52 -0
- package/lib/helpers/model/applyHooks.js +140 -0
- package/lib/helpers/model/applyMethods.js +70 -0
- package/lib/helpers/model/applyStaticHooks.js +33 -0
- package/lib/helpers/model/applyStatics.js +13 -0
- package/lib/helpers/model/castBulkWrite.js +316 -0
- package/lib/helpers/model/decorateBulkWriteResult.js +8 -0
- package/lib/helpers/model/discriminator.js +265 -0
- package/lib/helpers/model/pushNestedArrayPaths.js +15 -0
- package/lib/helpers/omitUndefined.js +20 -0
- package/lib/helpers/once.js +12 -0
- package/lib/helpers/parallelLimit.js +37 -0
- package/lib/helpers/path/parentPaths.js +18 -0
- package/lib/helpers/path/setDottedPath.js +33 -0
- package/lib/helpers/pluralize.js +95 -0
- package/lib/helpers/populate/assignRawDocsToIdStructure.js +129 -0
- package/lib/helpers/populate/assignVals.js +360 -0
- package/lib/helpers/populate/createPopulateQueryFilter.js +97 -0
- package/lib/helpers/populate/getModelsMapForPopulate.js +776 -0
- package/lib/helpers/populate/getSchemaTypes.js +228 -0
- package/lib/helpers/populate/getVirtual.js +103 -0
- package/lib/helpers/populate/leanPopulateMap.js +7 -0
- package/lib/helpers/populate/lookupLocalFields.js +40 -0
- package/lib/helpers/populate/markArraySubdocsPopulated.js +49 -0
- package/lib/helpers/populate/modelNamesFromRefPath.js +66 -0
- package/lib/helpers/populate/removeDeselectedForeignField.js +31 -0
- package/lib/helpers/populate/setPopulatedVirtualValue.js +33 -0
- package/lib/helpers/populate/skipPopulateValue.js +10 -0
- package/lib/helpers/populate/validateRef.js +19 -0
- package/lib/helpers/printJestWarning.js +21 -0
- package/lib/helpers/processConnectionOptions.js +65 -0
- package/lib/helpers/projection/applyProjection.js +83 -0
- package/lib/helpers/projection/hasIncludedChildren.js +41 -0
- package/lib/helpers/projection/isDefiningProjection.js +18 -0
- package/lib/helpers/projection/isExclusive.js +37 -0
- package/lib/helpers/projection/isInclusive.js +39 -0
- package/lib/helpers/projection/isNestedProjection.js +8 -0
- package/lib/helpers/projection/isPathExcluded.js +40 -0
- package/lib/helpers/projection/isPathSelectedInclusive.js +28 -0
- package/lib/helpers/projection/isSubpath.js +14 -0
- package/lib/helpers/projection/parseProjection.js +33 -0
- package/lib/helpers/query/applyGlobalOption.js +29 -0
- package/lib/helpers/query/cast$expr.js +287 -0
- package/lib/helpers/query/castFilterPath.js +54 -0
- package/lib/helpers/query/castUpdate.js +643 -0
- package/lib/helpers/query/getEmbeddedDiscriminatorPath.js +103 -0
- package/lib/helpers/query/handleImmutable.js +44 -0
- package/lib/helpers/query/handleReadPreferenceAliases.js +23 -0
- package/lib/helpers/query/hasDollarKeys.js +23 -0
- package/lib/helpers/query/isOperator.js +14 -0
- package/lib/helpers/query/sanitizeFilter.js +38 -0
- package/lib/helpers/query/sanitizeProjection.js +14 -0
- package/lib/helpers/query/selectPopulatedFields.js +62 -0
- package/lib/helpers/query/trusted.js +13 -0
- package/lib/helpers/query/validOps.js +3 -0
- package/lib/helpers/schema/addAutoId.js +7 -0
- package/lib/helpers/schema/applyBuiltinPlugins.js +12 -0
- package/lib/helpers/schema/applyPlugins.js +55 -0
- package/lib/helpers/schema/applyReadConcern.js +20 -0
- package/lib/helpers/schema/applyWriteConcern.js +39 -0
- package/lib/helpers/schema/cleanPositionalOperators.js +12 -0
- package/lib/helpers/schema/getIndexes.js +171 -0
- package/lib/helpers/schema/getKeysInSchemaOrder.js +28 -0
- package/lib/helpers/schema/getPath.js +43 -0
- package/lib/helpers/schema/getSubdocumentStrictValue.js +32 -0
- package/lib/helpers/schema/handleIdOption.js +20 -0
- package/lib/helpers/schema/handleTimestampOption.js +24 -0
- package/lib/helpers/schema/idGetter.js +34 -0
- package/lib/helpers/schema/merge.js +36 -0
- package/lib/helpers/schematype/handleImmutable.js +50 -0
- package/lib/helpers/setDefaultsOnInsert.js +158 -0
- package/lib/helpers/specialProperties.js +3 -0
- package/lib/helpers/symbols.js +20 -0
- package/lib/helpers/timers.js +3 -0
- package/lib/helpers/timestamps/setDocumentTimestamps.js +26 -0
- package/lib/helpers/timestamps/setupTimestamps.js +116 -0
- package/lib/helpers/topology/allServersUnknown.js +12 -0
- package/lib/helpers/topology/isAtlas.js +31 -0
- package/lib/helpers/topology/isSSLError.js +16 -0
- package/lib/helpers/update/applyTimestampsToChildren.js +193 -0
- package/lib/helpers/update/applyTimestampsToUpdate.js +131 -0
- package/lib/helpers/update/castArrayFilters.js +113 -0
- package/lib/helpers/update/decorateUpdateWithVersionKey.js +35 -0
- package/lib/helpers/update/modifiedPaths.js +33 -0
- package/lib/helpers/update/moveImmutableProperties.js +53 -0
- package/lib/helpers/update/removeUnusedArrayFilters.js +32 -0
- package/lib/helpers/update/updatedPathsByArrayFilter.js +27 -0
- package/lib/helpers/updateValidators.js +193 -0
- package/lib/index.js +17 -0
- package/lib/internal.js +46 -0
- package/lib/model.js +5010 -0
- package/lib/modifiedPathsSnapshot.js +9 -0
- package/lib/mongoose.js +1411 -0
- package/lib/options/populateOptions.js +36 -0
- package/lib/options/propertyOptions.js +8 -0
- package/lib/options/saveOptions.js +16 -0
- package/lib/options/schemaArrayOptions.js +78 -0
- package/lib/options/schemaBufferOptions.js +38 -0
- package/lib/options/schemaDateOptions.js +71 -0
- package/lib/options/schemaDocumentArrayOptions.js +68 -0
- package/lib/options/schemaMapOptions.js +43 -0
- package/lib/options/schemaNumberOptions.js +101 -0
- package/lib/options/schemaObjectIdOptions.js +64 -0
- package/lib/options/schemaStringOptions.js +138 -0
- package/lib/options/schemaSubdocumentOptions.js +66 -0
- package/lib/options/schemaTypeOptions.js +244 -0
- package/lib/options/schemaUnionOptions.js +32 -0
- package/lib/options/virtualOptions.js +164 -0
- package/lib/options.js +17 -0
- package/lib/plugins/index.js +6 -0
- package/lib/plugins/saveSubdocs.js +76 -0
- package/lib/plugins/sharding.js +84 -0
- package/lib/plugins/trackTransaction.js +84 -0
- package/lib/plugins/validateBeforeSave.js +41 -0
- package/lib/query.js +5673 -0
- package/lib/queryHelpers.js +387 -0
- package/lib/schema/array.js +699 -0
- package/lib/schema/bigint.js +282 -0
- package/lib/schema/boolean.js +332 -0
- package/lib/schema/buffer.js +343 -0
- package/lib/schema/date.js +467 -0
- package/lib/schema/decimal128.js +263 -0
- package/lib/schema/documentArray.js +656 -0
- package/lib/schema/documentArrayElement.js +137 -0
- package/lib/schema/double.js +246 -0
- package/lib/schema/index.js +32 -0
- package/lib/schema/int32.js +289 -0
- package/lib/schema/map.js +201 -0
- package/lib/schema/mixed.js +146 -0
- package/lib/schema/number.js +510 -0
- package/lib/schema/objectId.js +333 -0
- package/lib/schema/operators/bitwise.js +38 -0
- package/lib/schema/operators/exists.js +12 -0
- package/lib/schema/operators/geospatial.js +107 -0
- package/lib/schema/operators/helpers.js +32 -0
- package/lib/schema/operators/text.js +39 -0
- package/lib/schema/operators/type.js +20 -0
- package/lib/schema/string.js +733 -0
- package/lib/schema/subdocument.js +436 -0
- package/lib/schema/symbols.js +5 -0
- package/lib/schema/union.js +113 -0
- package/lib/schema/uuid.js +305 -0
- package/lib/schema.js +3226 -0
- package/lib/schemaType.js +1835 -0
- package/lib/stateMachine.js +232 -0
- package/lib/types/array/index.js +119 -0
- package/lib/types/array/isMongooseArray.js +5 -0
- package/lib/types/array/methods/index.js +1095 -0
- package/lib/types/arraySubdocument.js +207 -0
- package/lib/types/buffer.js +294 -0
- package/lib/types/decimal128.js +13 -0
- package/lib/types/documentArray/index.js +113 -0
- package/lib/types/documentArray/isMongooseDocumentArray.js +5 -0
- package/lib/types/documentArray/methods/index.js +415 -0
- package/lib/types/double.js +13 -0
- package/lib/types/index.js +23 -0
- package/lib/types/map.js +419 -0
- package/lib/types/objectid.js +41 -0
- package/lib/types/subdocument.js +464 -0
- package/lib/types/uuid.js +13 -0
- package/lib/utils.js +1054 -0
- package/lib/validOptions.js +42 -0
- package/lib/virtualType.js +204 -0
- package/package.json +148 -0
- package/types/aggregate.d.ts +180 -0
- package/types/augmentations.d.ts +9 -0
- package/types/callback.d.ts +8 -0
- package/types/collection.d.ts +49 -0
- package/types/connection.d.ts +297 -0
- package/types/cursor.d.ts +67 -0
- package/types/document.d.ts +374 -0
- package/types/error.d.ts +143 -0
- package/types/expressions.d.ts +3053 -0
- package/types/helpers.d.ts +32 -0
- package/types/index.d.ts +1056 -0
- package/types/indexes.d.ts +97 -0
- package/types/inferhydrateddoctype.d.ts +115 -0
- package/types/inferrawdoctype.d.ts +135 -0
- package/types/inferschematype.d.ts +337 -0
- package/types/middlewares.d.ts +59 -0
- package/types/models.d.ts +1306 -0
- package/types/mongooseoptions.d.ts +228 -0
- package/types/pipelinestage.d.ts +333 -0
- package/types/populate.d.ts +53 -0
- package/types/query.d.ts +934 -0
- package/types/schemaoptions.d.ts +282 -0
- package/types/schematypes.d.ts +654 -0
- package/types/session.d.ts +32 -0
- package/types/types.d.ts +109 -0
- package/types/utility.d.ts +175 -0
- package/types/validation.d.ts +39 -0
- package/types/virtuals.d.ts +14 -0
package/lib/aggregate.js
ADDED
|
@@ -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;
|