Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(model): add applyDefaults() helper that allows applying defaults to document or POJO #12057

Merged
merged 36 commits into from Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ae73d8b
feat(model): add `applyDefaults()` helper that allows applying defaul…
vkarpov15 Jul 6, 2022
dd49bc1
feat(model): correctly handle hydrated documents in `applyDefaults()`
vkarpov15 Jul 6, 2022
755511f
style(model): fix "Model.undefined" in the documentation for "Model.e…
hasezoey Jul 11, 2022
c04be0a
style(model): move jsdoc for "Model.prototype.increment" to the publi…
hasezoey Jul 11, 2022
93f3d8b
style(model): rename option "o" to "opts" for "Model.mapReduce"
hasezoey Jul 11, 2022
3090068
style(model): move option description to jsdoc tags for "Model.mapRed…
hasezoey Jul 11, 2022
60d9812
style(model): remove duplicated parameter for "Model.update"
hasezoey Jul 11, 2022
4873092
style(model): update alternative example for "Model.findOneAndRemove"…
hasezoey Jul 11, 2022
0e0598a
style(model): update alternative example for "Model.findOneAndDelete"…
hasezoey Jul 11, 2022
00ff60a
style(model): update alternative example for "Model.findByIdAndUpdate…
hasezoey Jul 11, 2022
1b4680e
style(model): move option description to jsdoc tags for "Model.findOn…
hasezoey Jul 11, 2022
ca5d2e4
style(model): move option description to jsdoc tags for "Model.findBy…
hasezoey Jul 11, 2022
429e7f9
style(model): move option description to jsdoc tags for "Model.findOn…
hasezoey Jul 11, 2022
9e60262
style(model): move option description to jsdoc tags for "Model.findOn…
hasezoey Jul 11, 2022
caf0e59
style(model): move option description to jsdoc tags for "Model.findBy…
hasezoey Jul 11, 2022
b63b289
style(model): change some jsdoc to be consistent
hasezoey Jul 11, 2022
82d964e
style(model): fix missing type for "Model" "fields"
hasezoey Jul 11, 2022
fc4ada4
style(model): update example for "Model.prototype.increment" to use a…
hasezoey Jul 11, 2022
3dd7b52
style(model): update example for "Model.prototype.remove" to use await
hasezoey Jul 11, 2022
099db00
style(model): fix jsdoc example (remove callback from promise)
hasezoey Jul 11, 2022
37b10af
fix: isAtlas check not working properly
skrtheboss Jul 15, 2022
48e9ebc
style: update "options.sort" and "options.select" type for suggestion
hasezoey Jul 15, 2022
9c129c0
style(model): move option description to jsdoc tags for "Model.findOn…
hasezoey Jul 15, 2022
bb19246
style(model): add "ReadPreference" as possible for "opts.readPreferen…
hasezoey Jul 15, 2022
14b175b
added sanitizeFilter() to mongoose.set() options
pathei-kosmos Jul 15, 2022
4160245
fix(model+timestamps): set timestamps on subdocuments in `insertMany()`
vkarpov15 Jul 15, 2022
86f66b0
fix(types): allow arbitrary expressions for ConcatArrays
vkarpov15 Jul 16, 2022
250b01b
fix(types): avoid treating `| undefined` types as `any` in `Require_i…
vkarpov15 Jul 16, 2022
1445c20
Merge pull request #12112 from pathei-kosmos/master
vkarpov15 Jul 16, 2022
f95373d
Merge pull request #12110 from skrtheboss/fix/is-atlas-check
vkarpov15 Jul 16, 2022
1306d00
Merge pull request #12086 from hasezoey/modelJSDOCTouchup
vkarpov15 Jul 16, 2022
201071b
fix(types): allow any value for AddFields
vkarpov15 Jul 17, 2022
9524f89
fix(types): make `$addToSet` fields mutable to allow programatically …
vkarpov15 Jul 17, 2022
0156d5e
style: fix lint
vkarpov15 Jul 17, 2022
e62d0ff
Merge branch 'master' into vkarpov15/gh-11945
vkarpov15 Jul 18, 2022
b165936
Merge branch '6.5' into vkarpov15/gh-11945
vkarpov15 Jul 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 5 additions & 119 deletions lib/document.js
Expand Up @@ -18,6 +18,7 @@ const ValidatorError = require('./error/validator');
const VirtualType = require('./virtualtype');
const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const applyDefaults = require('./helpers/document/applyDefaults');
const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
const compile = require('./helpers/document/compile').compile;
const defineKey = require('./helpers/document/compile').defineKey;
Expand Down Expand Up @@ -153,7 +154,7 @@ function Document(obj, fields, skipId, options) {
// By default, defaults get applied **before** setting initial values
// Re: gh-6155
if (defaults) {
$__applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
}
}
if (obj) {
Expand All @@ -177,7 +178,7 @@ function Document(obj, fields, skipId, options) {
this.$__.skipDefaults = options.skipDefaults;
}
} else if (defaults) {
$__applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
}

if (!this.$__.strictMode && obj) {
Expand Down Expand Up @@ -451,122 +452,6 @@ Object.defineProperty(Document.prototype, '$op', {
}
});

/*!
* ignore
*/

function $__applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
const paths = Object.keys(doc.$__schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let def;
let curPath = '';
const p = paths[i];

if (p === '_id' && doc.$__.skipId) {
continue;
}

const type = doc.$__schema.paths[p];
const path = type.splitPath();
const len = path.length;
let included = false;
let doc_ = doc._doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (exclude === true) {
if (curPath in fields) {
break;
}
} else if (exclude === false && fields && !included) {
const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
if (curPath in fields || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
included = true;
} else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
break;
}
}

if (j === len - 1) {
if (doc_[piece] !== void 0) {
break;
}

if (typeof type.defaultValue === 'function') {
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
break;
}
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
break;
}
} else if (!isBeforeSetters) {
// Non-function defaults should always run **before** setters
continue;
}

if (pathsToSkip && pathsToSkip[curPath]) {
break;
}

if (fields && exclude !== null) {
if (exclude === true) {
// apply defaults to all non-excluded fields
if (p in fields) {
continue;
}

try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
} else if (included) {
// selected field
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
doc_ = doc_[piece];
}
}
}
}

/*!
* ignore
*/
Expand Down Expand Up @@ -786,7 +671,8 @@ Document.prototype.$__init = function(doc, opts) {
const hasIncludedChildren = this.$__.exclude === false && this.$__.selected ?
$__hasIncludedChildren(this.$__.selected) :
null;
$__applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);

applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);

return this;
};
Expand Down
115 changes: 115 additions & 0 deletions lib/helpers/document/applyDefaults.js
@@ -0,0 +1,115 @@
'use strict';

module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
const paths = Object.keys(doc.$__schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let def;
let curPath = '';
const p = paths[i];

if (p === '_id' && doc.$__.skipId) {
continue;
}

const type = doc.$__schema.paths[p];
const path = type.splitPath();
const len = path.length;
let included = false;
let doc_ = doc._doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (exclude === true) {
if (curPath in fields) {
break;
}
} else if (exclude === false && fields && !included) {
const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
if (curPath in fields || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
included = true;
} else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
break;
}
}

if (j === len - 1) {
if (doc_[piece] !== void 0) {
break;
}

if (isBeforeSetters != null) {
if (typeof type.defaultValue === 'function') {
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
break;
}
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
break;
}
} else if (!isBeforeSetters) {
// Non-function defaults should always run **before** setters
continue;
}
}

if (pathsToSkip && pathsToSkip[curPath]) {
break;
}

if (fields && exclude !== null) {
if (exclude === true) {
// apply defaults to all non-excluded fields
if (p in fields) {
continue;
}

try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
} else if (included) {
// selected field
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
doc_ = doc_[piece];
}
}
}
};
52 changes: 52 additions & 0 deletions lib/helpers/model/applyDefaultsToPOJO.js
@@ -0,0 +1,52 @@
'use strict';

module.exports = function applyDefaultsToPOJO(doc, schema) {
const paths = Object.keys(schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let curPath = '';
const p = paths[i];

const type = schema.paths[p];
const path = type.splitPath();
const len = path.length;
let doc_ = doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (j === len - 1) {
if (typeof doc_[piece] !== 'undefined') {
if (type.$isSingleNested) {
applyDefaultsToPOJO(doc_[piece], type.caster.schema);
} else if (type.$isMongooseDocumentArray && Array.isArray(doc_[piece])) {
doc_[piece].forEach(el => applyDefaultsToPOJO(el, type.schema));
}

break;
}

const def = type.getDefault(doc, false, { skipCast: true });
if (typeof def !== 'undefined') {
doc_[piece] = def;

if (type.$isSingleNested) {
applyDefaultsToPOJO(def, type.caster.schema);
} else if (type.$isMongooseDocumentArray && Array.isArray(def)) {
def.forEach(el => applyDefaultsToPOJO(el, type.schema));
}
}
} else {
if (doc_[piece] == null) {
doc_[piece] = {};
}
doc_ = doc_[piece];
}
}
}
};
26 changes: 26 additions & 0 deletions lib/helpers/timestamps/setDocumentTimestamps.js
@@ -0,0 +1,26 @@
'use strict';

module.exports = function setDocumentTimestamps(doc, timestampOption, currentTime, createdAt, updatedAt) {
const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;

const defaultTimestamp = currentTime != null ?
currentTime() :
doc.ownerDocument().constructor.base.now();

if (!skipCreatedAt &&
(doc.isNew || doc.$isSubdocument) &&
createdAt &&
!doc.$__getValue(createdAt) &&
doc.$__isSelected(createdAt)) {
doc.$set(createdAt, defaultTimestamp, undefined, { overwriteImmutable: true });
}

if (!skipUpdatedAt && updatedAt && (doc.isNew || doc.$isModified())) {
let ts = defaultTimestamp;
if (doc.isNew && createdAt != null) {
ts = doc.$__getValue(createdAt);
}
doc.$set(updatedAt, ts);
}
};