@depup/mongoose 9.8.0-depup.0 → 9.9.0-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.
@@ -3,39 +3,101 @@
3
3
  const isNestedProjection = require('../projection/isNestedProjection');
4
4
 
5
5
  module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip, options) {
6
- const paths = Object.keys(doc.$__schema.paths);
7
- const plen = paths.length;
6
+ const schemaPaths = doc.$__schema.paths;
8
7
  const skipParentChangeTracking = options?.skipParentChangeTracking;
8
+ const skipId = doc.$__.skipId;
9
+ // Whether any path has a function default that must run **after** setters,
10
+ // so callers doing a before-setters pass can skip the after-setters pass
11
+ // entirely when this comes back `false`.
12
+ let hasPostSetterDefaults = false;
9
13
 
10
- for (let i = 0; i < plen; ++i) {
14
+ for (const p in schemaPaths) {
11
15
  let def;
12
16
  let curPath = '';
13
- const p = paths[i];
14
17
 
15
- if (p === '_id' && doc.$__.skipId) {
18
+ if (p === '_id' && skipId) {
16
19
  continue;
17
20
  }
18
21
 
19
- const type = doc.$__schema.paths[p];
22
+ const type = schemaPaths[p];
20
23
  // `getDefault()` returns undefined when `defaultValue` is undefined, in which
21
24
  // case this loop never modifies the doc, so skip traversing the path entirely.
22
25
  if (type.defaultValue === undefined) {
23
26
  continue;
24
27
  }
28
+ if (typeof type.defaultValue === 'function' && !type.defaultValue.$runBeforeSetters) {
29
+ hasPostSetterDefaults = true;
30
+ }
25
31
  const path = type.splitPath();
26
32
  const len = path.length;
27
33
  if (path[len - 1] === '$*') {
28
34
  continue;
29
35
  }
36
+
30
37
  let included = false;
31
38
  let doc_ = doc._doc;
39
+
40
+ if (len === 1) {
41
+ if (exclude === true) {
42
+ if (p in fields) {
43
+ continue;
44
+ }
45
+ } else if (exclude === false && fields) {
46
+ const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
47
+ if ((p in fields && !isNestedProjection(fields[p])) || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[p])) {
48
+ included = true;
49
+ } else if (hasIncludedChildren != null && !hasIncludedChildren[p]) {
50
+ continue;
51
+ }
52
+ }
53
+
54
+ if (doc_[p] !== void 0) {
55
+ continue;
56
+ }
57
+
58
+ if (isBeforeSetters != null) {
59
+ if (typeof type.defaultValue === 'function') {
60
+ if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
61
+ continue;
62
+ }
63
+ if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
64
+ continue;
65
+ }
66
+ } else if (!isBeforeSetters) {
67
+ // Non-function defaults should always run **before** setters
68
+ continue;
69
+ }
70
+ }
71
+
72
+ if (pathsToSkip && pathsToSkip[p]) {
73
+ continue;
74
+ }
75
+
76
+ if (fields && exclude !== null && !(exclude === true || included)) {
77
+ continue;
78
+ }
79
+
80
+ try {
81
+ def = type.getDefault(doc, false);
82
+ } catch (err) {
83
+ doc.invalidate(p, err);
84
+ continue;
85
+ }
86
+
87
+ if (typeof def !== 'undefined') {
88
+ doc_[p] = def;
89
+ applyChangeTracking(doc, p, skipParentChangeTracking);
90
+ }
91
+ continue;
92
+ }
93
+
32
94
  for (let j = 0; j < len; ++j) {
33
95
  if (doc_ == null) {
34
96
  break;
35
97
  }
36
98
 
37
99
  const piece = path[j];
38
- curPath += (!curPath.length ? '' : '.') + piece;
100
+ curPath = curPath.length ? curPath + '.' + piece : piece;
39
101
 
40
102
  if (exclude === true) {
41
103
  if (curPath in fields) {
@@ -123,6 +185,8 @@ module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildre
123
185
  }
124
186
  }
125
187
  }
188
+
189
+ return hasPostSetterDefaults;
126
190
  };
127
191
 
128
192
  /*!
@@ -127,7 +127,7 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
127
127
  applyTimestampsToUpdate(now, createdAt, updatedAt, update, {
128
128
  timestamps: updateOne.timestamps,
129
129
  overwriteImmutable: updateOne.overwriteImmutable
130
- });
130
+ }, updateOne.upsert);
131
131
  }
132
132
 
133
133
  if (doInitTimestamps) {
@@ -194,7 +194,7 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
194
194
  applyTimestampsToUpdate(now, createdAt, updatedAt, updateMany['update'], {
195
195
  timestamps: updateMany.timestamps,
196
196
  overwriteImmutable: updateMany.overwriteImmutable
197
- });
197
+ }, updateMany.upsert);
198
198
  }
199
199
  if (doInitTimestamps) {
200
200
  applyTimestampsToChildren(now, updateMany['update'], model.schema);
@@ -3,7 +3,9 @@
3
3
  module.exports = parallelLimit;
4
4
 
5
5
  /*!
6
- * ignore
6
+ * Run `fn` over every entry of `params` with at most `limit` calls in flight at
7
+ * a time, resolving to the results in input order. Currently only used by
8
+ * `Model.insertMany()` to bound how many documents validate concurrently.
7
9
  */
8
10
 
9
11
  async function parallelLimit(params, fn, limit) {
@@ -11,27 +13,50 @@ async function parallelLimit(params, fn, limit) {
11
13
  throw new Error('Limit must be positive');
12
14
  }
13
15
 
14
- if (params.length === 0) {
16
+ const length = params.length;
17
+ if (length === 0) {
15
18
  return [];
16
19
  }
17
20
 
18
- const results = [];
19
- const executing = new Set();
20
-
21
- for (let index = 0; index < params.length; index++) {
22
- const param = params[index];
23
- const p = fn(param, index);
24
- results.push(p);
25
-
26
- executing.add(p);
21
+ const results = new Array(length);
22
+ // Fast path: there's nothing to bound, so kick everything off and wait once.
23
+ // Avoids the per-task `Set` + `Promise.race` bookkeeping of a sliding window.
24
+ if (limit >= length) {
25
+ for (let i = 0; i < length; ++i) {
26
+ results[i] = fn(params[i], i);
27
+ }
28
+ return Promise.all(results);
29
+ }
27
30
 
28
- const clean = () => executing.delete(p);
29
- p.then(clean).catch(clean);
31
+ let nextIndex = 0;
32
+ let firstError = null;
30
33
 
31
- if (executing.size >= limit) {
32
- await Promise.race(executing);
34
+ function worker() {
35
+ if (nextIndex >= length || firstError !== null) {
36
+ return undefined;
33
37
  }
38
+ const index = nextIndex++;
39
+ return Promise.resolve(fn(params[index], index)).then(
40
+ (val) => {
41
+ results[index] = val;
42
+ return worker();
43
+ },
44
+ (err) => {
45
+ if (firstError === null) {
46
+ firstError = err;
47
+ }
48
+ }
49
+ );
34
50
  }
35
51
 
36
- return Promise.all(results);
52
+ const workers = new Array(Math.min(limit, length));
53
+ for (let i = 0; i < workers.length; ++i) {
54
+ workers[i] = worker();
55
+ }
56
+ await Promise.all(workers);
57
+
58
+ if (firstError !== null) {
59
+ throw firstError;
60
+ }
61
+ return results;
37
62
  }
@@ -11,6 +11,7 @@ const getConstructorName = require('../getConstructorName');
11
11
  const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
12
12
  const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
13
13
  const handleImmutable = require('./handleImmutable');
14
+ const isOperator = require('./isOperator');
14
15
  const moveImmutableProperties = require('../update/moveImmutableProperties');
15
16
  const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
16
17
  const setDottedPath = require('../path/setDottedPath');
@@ -235,10 +236,25 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
235
236
  key = keys[i];
236
237
  val = obj[key];
237
238
 
239
+ const fullPath = prefix + key;
240
+ const isTopLevelOperator = !prefix && isOperator(key);
241
+ let fullPathSchema = isTopLevelOperator ? schema._getSchema(fullPath) : null;
242
+ const isTopLevelNestedDollarPath = isTopLevelOperator && Object.hasOwn(schema.nested, key);
243
+ if (isTopLevelOperator &&
244
+ fullPathSchema == null &&
245
+ !isTopLevelNestedDollarPath) {
246
+ throw new MongooseError('Invalid update: Unexpected modifier "' + key + '" as a key in operator "' + op + '". '
247
+ + 'Did you mean something like { ' + op + ': { fieldName: { ' + key + ': [...] } } }? '
248
+ + 'Modifiers must appear under a valid field path.');
249
+ }
250
+
238
251
  // `$pull` is special because we need to cast the RHS as a query, not as
239
252
  // an update.
240
253
  if (op === '$pull') {
241
- schematype = schema._getSchema(prefix + key);
254
+ if (!isTopLevelOperator) {
255
+ fullPathSchema = schema._getSchema(fullPath);
256
+ }
257
+ schematype = fullPathSchema;
242
258
  if (schematype == null) {
243
259
  const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options);
244
260
  if (_res.schematype != null) {
@@ -269,10 +285,13 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
269
285
  }
270
286
  }
271
287
 
288
+ if (!isTopLevelOperator && op !== '$pull') {
289
+ fullPathSchema = schema._getSchema(fullPath);
290
+ }
291
+ schematype = fullPathSchema;
292
+
272
293
  if (getConstructorName(val) === 'Object') {
273
294
  // watch for embedded doc schemas
274
- schematype = schema._getSchema(prefix + key);
275
-
276
295
  if (schematype == null) {
277
296
  const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options);
278
297
  if (_res.schematype != null) {
@@ -383,14 +402,12 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
383
402
  (utils.isObject(val) && utils.hasOwnKeys(val) === false);
384
403
  }
385
404
  } else {
386
- const isModifier = (key === '$each' || key === '$or' || key === '$and' || key === '$in');
387
- if (isModifier && !prefix) {
388
- throw new MongooseError('Invalid update: Unexpected modifier "' + key + '" as a key in operator. '
389
- + 'Did you mean something like { $addToSet: { fieldName: { $each: [...] } } }? '
390
- + 'Modifiers such as "$each", "$or", "$and", "$in" must appear under a valid field path.');
405
+ const isModifier = !isTopLevelNestedDollarPath && schematype == null &&
406
+ (key === '$each' || key === '$or' || key === '$and' || key === '$in');
407
+ const checkPath = isModifier ? prefix : fullPath;
408
+ if (isModifier) {
409
+ schematype = schema._getSchema(checkPath);
391
410
  }
392
- const checkPath = isModifier ? prefix : prefix + key;
393
- schematype = schema._getSchema(checkPath);
394
411
 
395
412
  // You can use `$setOnInsert` with immutable keys
396
413
  if (op !== '$setOnInsert' &&
@@ -26,6 +26,9 @@ module.exports = function addIdGetter(schema) {
26
26
  */
27
27
 
28
28
  function idGetter() {
29
+ if (this._doc) {
30
+ return this._doc._id?.toString() ?? null;
31
+ }
29
32
  if (this._id != null) {
30
33
  return this._id.toString();
31
34
  }
@@ -123,6 +123,7 @@ function _setTimestampsOnUpdate() {
123
123
  updatedAt,
124
124
  this.getUpdate(),
125
125
  this._mongooseOptions,
126
+ this.options.upsert,
126
127
  replaceOps.has(this.op)
127
128
  );
128
129
  applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
@@ -4,7 +4,6 @@
4
4
  * ignore
5
5
  */
6
6
 
7
- const get = require('../get');
8
7
  const utils = require('../../utils');
9
8
 
10
9
  module.exports = applyTimestampsToUpdate;
@@ -13,10 +12,10 @@ module.exports = applyTimestampsToUpdate;
13
12
  * ignore
14
13
  */
15
14
 
16
- function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options, isReplace) {
15
+ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options, upsert, isReplace) {
17
16
  const updates = currentUpdate;
18
17
  let _updates = updates;
19
- const timestamps = get(options, 'timestamps', true);
18
+ const timestamps = options?.timestamps ?? true;
20
19
 
21
20
  // Support skipping timestamps at the query level, see gh-6980
22
21
  if (!timestamps || updates == null) {
@@ -81,7 +80,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
81
80
  }
82
81
 
83
82
  if (!skipCreatedAt && createdAt) {
84
- const overwriteImmutable = get(options, 'overwriteImmutable', false);
83
+ const overwriteImmutable = options?.overwriteImmutable;
85
84
  const hasUserCreatedAt = currentUpdate[createdAt] != null
86
85
  || currentUpdate.$set?.[createdAt] != null
87
86
  || currentUpdate.$setOnInsert?.[createdAt] != null;
@@ -119,7 +118,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
119
118
  }
120
119
  }
121
120
 
122
- if (!timestampSet) {
121
+ if (!timestampSet && upsert) {
123
122
  updates.$setOnInsert = updates.$setOnInsert || {};
124
123
  updates.$setOnInsert[createdAt] = now;
125
124
  }
package/lib/model.js CHANGED
@@ -413,8 +413,9 @@ Model.prototype.$__save = async function $__save(options) {
413
413
  const saveOptions = _createSaveOptions(this, options);
414
414
 
415
415
  if (this.$isNew) {
416
+ const hasOnlyPrimitiveValues = this.$__hasOnlyPrimitiveValues();
416
417
  // send entire doc
417
- const obj = this.$__hasOnlyPrimitiveValues() ?
418
+ const obj = hasOnlyPrimitiveValues ?
418
419
  this.$__toObjectShallow() :
419
420
  this.toObject(saveToObjectOptions);
420
421
  if ((obj || {})._id === void 0) {
@@ -428,11 +429,11 @@ Model.prototype.$__save = async function $__save(options) {
428
429
 
429
430
  this.$__version(true, obj);
430
431
  this.$__reset();
431
- _setIsNew(this, false);
432
+ _setIsNew(this, false, hasOnlyPrimitiveValues);
432
433
  // Make it possible to retry the insert
433
434
  this.$__.inserting = true;
434
435
  result = await this[modelCollectionSymbol].insertOne(obj, saveOptions).catch(err => {
435
- _setIsNew(this, true);
436
+ _setIsNew(this, true, hasOnlyPrimitiveValues);
436
437
  throw err;
437
438
  });
438
439
  } else {
@@ -3044,23 +3045,26 @@ Model.insertMany = async function insertMany(arr, options) {
3044
3045
 
3045
3046
  async function _insertMany(arr, options) {
3046
3047
  options = options || {};
3047
- const preFilter = buildMiddlewareFilter(options, 'pre');
3048
- const postFilter = buildMiddlewareFilter(options, 'post');
3048
+ const hasInsertManyHooks = this._middleware.hasHooks('insertMany');
3049
+ const preFilter = hasInsertManyHooks ? buildMiddlewareFilter(options, 'pre') : null;
3050
+ const postFilter = hasInsertManyHooks ? buildMiddlewareFilter(options, 'post') : null;
3049
3051
 
3050
- try {
3051
- [arr] = await this._middleware.execPre('insertMany', this, [arr], { filter: preFilter });
3052
- } catch (error) {
3053
- await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
3052
+ if (hasInsertManyHooks) {
3053
+ try {
3054
+ [arr] = await this._middleware.execPre('insertMany', this, [arr], { filter: preFilter });
3055
+ } catch (error) {
3056
+ await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
3057
+ }
3054
3058
  }
3055
3059
  const ThisModel = this;
3056
3060
  const limit = options.limit || 1000;
3057
3061
  const rawResult = !!options.rawResult;
3058
- const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
3059
- const throwOnValidationError = typeof options.throwOnValidationError === 'boolean' ? options.throwOnValidationError : false;
3062
+ const ordered = options.ordered == null ? true : !!options.ordered;
3063
+ const throwOnValidationError = !!options.throwOnValidationError;
3060
3064
  const lean = !!options.lean;
3061
3065
 
3062
3066
  const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
3063
- if ((!options || !Object.hasOwn(options, 'session')) && asyncLocalStorage?.session != null) {
3067
+ if (!Object.hasOwn(options, 'session') && asyncLocalStorage?.session != null) {
3064
3068
  options = { ...options, session: asyncLocalStorage.session };
3065
3069
  }
3066
3070
 
@@ -3069,9 +3073,9 @@ async function _insertMany(arr, options) {
3069
3073
  }
3070
3074
 
3071
3075
  const validationErrors = [];
3072
- const validationErrorsToOriginalOrder = new Map();
3073
3076
  const results = ordered ? null : new Array(arr.length);
3074
- async function validateDoc(doc, index) {
3077
+ const session = options.session;
3078
+ function validateDoc(doc, index) {
3075
3079
  // If option `lean` is set to true bypass validation and hydration
3076
3080
  if (lean) {
3077
3081
  return doc;
@@ -3079,14 +3083,14 @@ async function _insertMany(arr, options) {
3079
3083
  let createdNewDoc = false;
3080
3084
  if (!(doc instanceof ThisModel)) {
3081
3085
  if (doc != null && typeof doc !== 'object') {
3082
- throw new ObjectParameterError(doc, 'arr.' + index, 'insertMany');
3086
+ return Promise.reject(new ObjectParameterError(doc, 'arr.' + index, 'insertMany'));
3083
3087
  }
3084
3088
  doc = new ThisModel(doc);
3085
3089
  createdNewDoc = true;
3086
3090
  }
3087
3091
 
3088
- if (options.session != null) {
3089
- doc.$session(options.session);
3092
+ if (session != null) {
3093
+ doc.$session(session);
3090
3094
  }
3091
3095
  return doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null)
3092
3096
  .then(() => doc)
@@ -3094,7 +3098,6 @@ async function _insertMany(arr, options) {
3094
3098
  if (ordered === false) {
3095
3099
  error.index = index;
3096
3100
  validationErrors.push(error);
3097
- validationErrorsToOriginalOrder.set(error, index);
3098
3101
  results[index] = error;
3099
3102
  return;
3100
3103
  }
@@ -3104,27 +3107,42 @@ async function _insertMany(arr, options) {
3104
3107
 
3105
3108
  const docs = await parallelLimit(arr, validateDoc, limit);
3106
3109
 
3107
- const originalDocIndex = new Map();
3108
- const validDocIndexToOriginalIndex = new Map();
3110
+ const validDocIndexToOriginalIndex = {};
3111
+ const docAttributes = [];
3112
+ const docObjects = lean ? docAttributes : [];
3113
+ const versionKeyPath = ThisModel.schema.options.versionKey
3114
+ ? ThisModel.schema.paths[ThisModel.schema.options.versionKey]?.splitPath()
3115
+ : null;
3116
+ const timestamps = options.timestamps;
3109
3117
  for (let i = 0; i < docs.length; ++i) {
3110
- originalDocIndex.set(docs[i], i);
3111
- }
3118
+ const doc = docs[i];
3119
+ if (doc == null) {
3120
+ continue;
3121
+ }
3122
+ validDocIndexToOriginalIndex[docAttributes.length] = i;
3123
+ docAttributes.push(doc);
3112
3124
 
3113
- // We filter all failed pre-validations by removing nulls
3114
- const docAttributes = docs.filter(function(doc) {
3115
- return doc != null;
3116
- });
3117
- for (let i = 0; i < docAttributes.length; ++i) {
3118
- validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
3125
+ // In lean mode `docObjects === docAttributes`, so the push above is enough.
3126
+ if (lean) {
3127
+ continue;
3128
+ }
3129
+ if (versionKeyPath) {
3130
+ doc.$__setValue(versionKeyPath, 0);
3131
+ }
3132
+ const shouldSetTimestamps = timestamps !== false && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
3133
+ if (shouldSetTimestamps) {
3134
+ doc.initializeTimestamps(timestamps);
3135
+ }
3136
+ docObjects.push(doc.$__hasOnlyPrimitiveValues() ? doc.$__toObjectShallow() : doc.toObject(internalToObjectOptions));
3119
3137
  }
3120
3138
 
3121
3139
  // Make sure validation errors are in the same order as the
3122
3140
  // original documents, so if both doc1 and doc2 both fail validation,
3123
3141
  // `Model.insertMany([doc1, doc2])` will always have doc1's validation
3124
3142
  // error before doc2's. Re: gh-12791.
3125
- if (validationErrors.length > 0) {
3143
+ if (validationErrors.length > 0 && ordered === false) {
3126
3144
  validationErrors.sort((err1, err2) => {
3127
- return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
3145
+ return err1.index - err2.index;
3128
3146
  });
3129
3147
  }
3130
3148
 
@@ -3149,19 +3167,6 @@ async function _insertMany(arr, options) {
3149
3167
  }
3150
3168
  return [];
3151
3169
  }
3152
- const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
3153
- if (doc.$__schema.options.versionKey) {
3154
- doc[doc.$__schema.options.versionKey] = 0;
3155
- }
3156
- const shouldSetTimestamps = options?.timestamps !== false && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
3157
- if (shouldSetTimestamps) {
3158
- doc.initializeTimestamps(options?.timestamps);
3159
- }
3160
- if (doc.$__hasOnlyPrimitiveValues()) {
3161
- return doc.$__toObjectShallow();
3162
- }
3163
- return doc.toObject(internalToObjectOptions);
3164
- });
3165
3170
 
3166
3171
  let res;
3167
3172
  try {
@@ -3180,7 +3185,7 @@ async function _insertMany(arr, options) {
3180
3185
 
3181
3186
  if (error.writeErrors != null) {
3182
3187
  for (let i = 0; i < error.writeErrors.length; ++i) {
3183
- const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3188
+ const originalIndex = validDocIndexToOriginalIndex[error.writeErrors[i].index];
3184
3189
  error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
3185
3190
  if (!ordered) {
3186
3191
  results[originalIndex] = error.writeErrors[i];
@@ -3219,7 +3224,7 @@ async function _insertMany(arr, options) {
3219
3224
  if (lean) {
3220
3225
  return doc;
3221
3226
  }
3222
- doc.$__reset();
3227
+ doc.$__reset(true);
3223
3228
  _setIsNew(doc, false);
3224
3229
  return doc;
3225
3230
  });
@@ -3228,13 +3233,18 @@ async function _insertMany(arr, options) {
3228
3233
  decorateBulkWriteResult(error, validationErrors, results);
3229
3234
  }
3230
3235
 
3231
- await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
3236
+ if (hasInsertManyHooks) {
3237
+ await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
3238
+ } else {
3239
+ throw error;
3240
+ }
3232
3241
  }
3233
3242
 
3234
3243
  if (!lean) {
3235
3244
  for (const attribute of docAttributes) {
3236
- attribute.$__reset();
3237
- _setIsNew(attribute, false);
3245
+ const hasOnlyPrimitiveValues = attribute.$__hasOnlyPrimitiveValues();
3246
+ attribute.$__reset(true, hasOnlyPrimitiveValues);
3247
+ _setIsNew(attribute, false, hasOnlyPrimitiveValues);
3238
3248
  }
3239
3249
  }
3240
3250
 
@@ -3276,19 +3286,28 @@ async function _insertMany(arr, options) {
3276
3286
  });
3277
3287
  }
3278
3288
 
3279
- const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
3280
- return result;
3289
+ if (hasInsertManyHooks) {
3290
+ const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
3291
+ return result;
3292
+ }
3293
+ return docAttributes;
3281
3294
  }
3282
3295
 
3283
3296
  /*!
3284
- * ignore
3297
+ * @param {Document} doc The document to set `$isNew` on.
3298
+ * @param {boolean} val The value to set `$isNew` to.
3299
+ * @param {boolean} [hasOnlyPrimitiveValues] Skip getting subdocs for performance if we know there are none.
3300
+ * @api private
3285
3301
  */
3286
3302
 
3287
- function _setIsNew(doc, val) {
3303
+ function _setIsNew(doc, val, hasOnlyPrimitiveValues) {
3288
3304
  doc.$isNew = val;
3289
3305
  doc.$emit('isNew', val);
3290
3306
  doc.constructor.emit('isNew', val);
3291
3307
 
3308
+ if (hasOnlyPrimitiveValues) {
3309
+ return;
3310
+ }
3292
3311
  const subdocs = doc.$getAllSubdocs({ useCache: true });
3293
3312
  for (const subdoc of subdocs) {
3294
3313
  subdoc.$isNew = val;
package/lib/schema.js CHANGED
@@ -727,7 +727,7 @@ Schema.prototype._getDocumentMiddleware = function _getDocumentMiddleware() {
727
727
 
728
728
  Schema.prototype._defaultToObjectOptions = function(json) {
729
729
  const path = json ? 'toJSON' : 'toObject';
730
- if (this._defaultToObjectOptionsMap && this._defaultToObjectOptionsMap[path]) {
730
+ if (this._defaultToObjectOptionsMap && path in this._defaultToObjectOptionsMap) {
731
731
  return this._defaultToObjectOptionsMap[path];
732
732
  }
733
733
 
@@ -738,7 +738,7 @@ Schema.prototype._defaultToObjectOptions = function(json) {
738
738
  const defaultOptions = Object.assign({}, baseOptions, schemaOptions);
739
739
 
740
740
  this._defaultToObjectOptionsMap = this._defaultToObjectOptionsMap || {};
741
- this._defaultToObjectOptionsMap[path] = defaultOptions;
741
+ this._defaultToObjectOptionsMap[path] = utils.hasOwnKeys(defaultOptions) ? defaultOptions : null;
742
742
  return defaultOptions;
743
743
  };
744
744
 
@@ -1870,6 +1870,46 @@ Schema.prototype.requiredPaths = function requiredPaths(invalidate) {
1870
1870
  return this._requiredpaths;
1871
1871
  };
1872
1872
 
1873
+ /**
1874
+ * Returns an Array of path strings that have a `transform` option, either on the
1875
+ * path itself or on the path's `embeddedSchemaType` for arrays.
1876
+ *
1877
+ * #### Example:
1878
+ *
1879
+ * const s = new Schema({
1880
+ * name: { type: String, transform: v => v.toLowerCase() },
1881
+ * tags: [{ type: String, transform: v => v.trim() }],
1882
+ * age: { type: Number, required: true },
1883
+ * notes: String
1884
+ * });
1885
+ * s.pathsWithTransforms(); // [ 'name', 'tags' ]
1886
+ *
1887
+ * @api private
1888
+ * @param {boolean} invalidate Refresh the cache
1889
+ * @return {Array|null}
1890
+ */
1891
+
1892
+ Schema.prototype.pathsWithTransforms = function pathsWithTransforms(invalidate) {
1893
+ if (this._pathsWithTransforms !== undefined && !invalidate) {
1894
+ return this._pathsWithTransforms;
1895
+ }
1896
+
1897
+ const paths = Object.keys(this.paths);
1898
+ const ret = [];
1899
+
1900
+ for (const path of paths) {
1901
+ const schematype = this.paths[path];
1902
+ const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
1903
+ const embeddedSchemaTypeTransformFunction = schematype.embeddedSchemaType?.options?.transform
1904
+ ?? schematype.embeddedSchemaType?.constructor?.defaultOptions?.transform;
1905
+ if (topLevelTransformFunction || embeddedSchemaTypeTransformFunction) {
1906
+ ret.push(path);
1907
+ }
1908
+ }
1909
+ this._pathsWithTransforms = ret.length ? ret : null;
1910
+ return this._pathsWithTransforms;
1911
+ };
1912
+
1873
1913
  /**
1874
1914
  * Returns indexes from fields and schema-level indexes (cached).
1875
1915
  *
@@ -3097,6 +3137,8 @@ function isArrayFilter(piece) {
3097
3137
  Schema.prototype._preCompile = function _preCompile() {
3098
3138
  this.plugin(idGetter, { deduplicate: true });
3099
3139
  _precomputeOptimisticConcurrency(this);
3140
+ // Cache paths that have transforms
3141
+ this.pathsWithTransforms();
3100
3142
  };
3101
3143
 
3102
3144
  /*!