-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
109 lines (90 loc) · 4.48 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
const each = require('lodash.foreach');
const get = require('lodash.get');
// Function typecheck helper
const isFunc = (val) => typeof val === 'function';
const deepPath = function(schema, pathName) {
let path;
const paths = pathName.split('.');
if (paths.length > 1) {
pathName = paths.shift();
}
if (isFunc(schema.path)) {
path = schema.path(pathName);
}
if (path && path.schema) {
path = deepPath(path.schema, paths.join('.'));
}
return path;
};
// Export the mongoose plugin
module.exports = function(schema, options) {
options = options || {};
const type = options.type || 'unique';
const message = options.message || 'Error, expected `{PATH}` to be unique. Value: `{VALUE}`';
// Mongoose Schema objects don't describe default _id indexes
// https://github.com/Automattic/mongoose/issues/5998
const indexes = [[{ _id: 1 }, { unique: true }]].concat(schema.indexes());
// Dynamically iterate all indexes
each(indexes, (index) => {
const indexOptions = index[1];
if (indexOptions.unique) {
const paths = Object.keys(index[0]);
each(paths, (pathName) => {
// Choose error message
const pathMessage = typeof indexOptions.unique === 'string' ? indexOptions.unique : message;
// Obtain the correct path object
const path = deepPath(schema, pathName) || schema.path(pathName);
if (path) {
// Add an async validator
path.validate(function() {
return new Promise((resolve) => {
const isSubdocument = isFunc(this.ownerDocument);
const isQuery = this.constructor.name === 'Query';
const parentDoc = isSubdocument ? this.ownerDocument() : this;
const isNew = typeof parentDoc.isNew === 'boolean' ? parentDoc.isNew : !isQuery;
let conditions = [];
each(paths, (name) => {
let pathValue;
// If the doc is a query, this is a findAndUpdate
if (isQuery) {
pathValue = get(this, '_update.' + name) || get(this, '_update.$set.' + name);
} else {
pathValue = get(this, isSubdocument ? name.split('.').pop() : name);
}
// Wrap with case-insensitivity
if (get(path, 'options.uniqueCaseInsensitive') || indexOptions.uniqueCaseInsensitive) {
pathValue = new RegExp('^' + pathValue + '$', 'i');
}
conditions.push({ [name]: pathValue });
});
if (!isNew) {
// Use conditions the user has with find*AndUpdate
if (isQuery) {
each(this._conditions, (value, key) => {
conditions.push({ [key]: { $ne: value } });
});
} else if (this._id) {
conditions.push({ _id: { $ne: this._id } });
}
}
// Obtain the model depending on context
// https://github.com/Automattic/mongoose/issues/3430
// https://github.com/Automattic/mongoose/issues/3589
let model;
if (isQuery) {
model = this.model;
} else if (isSubdocument) {
model = this.ownerDocument().model(this.ownerDocument().constructor.modelName);
} else if (isFunc(this.model)) {
model = this.model(this.constructor.modelName);
}
model.where({ $and: conditions }).count((err, count) => {
resolve(count === 0);
});
});
}, pathMessage, type);
}
});
}
});
};