forked from nassor/mongoose-auto-increment
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
230 lines (203 loc) · 9.44 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Module Scope
var mongoose = require('mongoose');
var _ = require('lodash');
var IdentityCounter;
var counterSchema = new mongoose.Schema({
model: { type: String, required: true },
field: { type: String, required: true },
groupingField: { type: String, default: '' },
count: { type: Number, default: 0 }
});
counterSchema.index({
field: 1,
groupingField: 1,
model: 1
}, {
unique: true
});
// Initialize plugin by creating counter collection in database.
exports.initialize = function initialize(connection) {
try {
IdentityCounter = connection.model('IdentityCounter');
} catch (ex) {
if (ex.name === 'MissingSchemaError') {
// Create model using new schema.
IdentityCounter = connection.model('IdentityCounter', counterSchema);
} else {
throw ex;
}
}
};
// The function to use when invoking the plugin on a custom schema.
exports.plugin = function plugin(schema, options) {
var compoundIndex = {};
var fields = {}; // A hash of fields to add properties to in Mongoose.
// Default settings and plugin scope variables.
var settings = {
migrate: false, // If this is to be run on a migration for existing records. Only set this on migration processes.
model: null, // The model to configure the plugin for.
field: '_id', // The field the plugin should track.
groupingField: '', // The field by which to group documents, allowing for each grouping to be incremented separately.
startAt: 0, // The number the count should start at.
incrementBy: 1, // The number by which to increment the count each time.
unique: true, // Should we create a unique index for the field,
outputFilter: undefined // function that modifies the output of the counter.
};
// If we don't have reference to the counterSchema or the IdentityCounter model then the plugin was most likely not
// initialized properly so throw an error.
if (!counterSchema || !IdentityCounter) {
throw new Error('mongoose-auto-increment has not been initialized');
}
switch (typeof(options)) {
// If string, the user chose to pass in just the model name.
case 'string':
settings.model = options;
break;
// If object, the user passed in a hash of options.
case 'object':
_.assign(settings, options);
break;
}
if (typeof settings.model !== 'string') {
throw new Error('model must be set');
}
if (settings.field === '_id') {
if (settings.groupingField.length) {
throw new Error('Cannot use a grouping field with _id, choose a different field name.');
}
}
if (!schema.path(settings.field) || settings.field === '_id') {
schema.add(_.set({}, settings.field, { type: Number }));
}
// If a groupingField is specified, create a compound unique index.
if (settings.groupingField.length) {
compoundIndex[settings.field] = 1;
compoundIndex[settings.groupingField] = 1;
schema.index(compoundIndex, { unique: settings.unique });
// Otherwise, add the unique index directly to the custom field.
} else {
// Add properties for field in schema.
schema.path(settings.field).index({ unique: settings.unique });
}
// Add nextCount as both a method on documents and a static on the schema for convenience.
schema.method('nextCount', nextCount);
schema.static('nextCount', nextCount);
// Add resetCount as both a method on documents and a static on the schema for convenience.
schema.method('resetCount', resetCount);
schema.static('resetCount', resetCount);
// Every time documents in this schema are saved, run this logic.
schema.pre('validate', function (next) {
// Get reference to the document being saved.
var doc = this;
var ready = false; // True if the counter collection has been updated and the document is ready to be saved.
var ranOnce = doc.__maiRanOnce === true;
// Only do this if it is a new document & the field doesn't have a value set (see http://mongoosejs.com/docs/api.html#document_Document-isNew)
if ((doc.isNew && ranOnce === false) || settings.migrate) {
(function save() {
// Find the counter for this model and the relevant field.
IdentityCounter.findOne({
model: settings.model,
field: settings.field,
groupingField: doc.get(settings.groupingField) || ''
}).exec().then(function (counter) {
if (counter) {
return counter;
}
// If no counter exists then create one and save it.
counter = new IdentityCounter({
model: settings.model,
field: settings.field,
groupingField: doc.get(settings.groupingField) || '',
count: settings.startAt - settings.incrementBy
});
return counter.save();
}).then(function updateCounter(counter) {
// check that a number has already been provided, and update the counter to that number if it is
// greater than the current count
if (typeof doc.get(settings.field) === 'number') {
return IdentityCounter.findOneAndUpdate({
model: settings.model,
field: settings.field,
groupingField: doc.get(settings.groupingField) || '',
count: { $lt: doc.get(settings.field) }
}, {
// Change the count of the value found to the new field value.
count: doc.get(settings.field)
}).exec();
} else {
// Find the counter collection entry for this model and field and update it.
return IdentityCounter.findOneAndUpdate({
model: settings.model,
field: settings.field,
groupingField: doc.get(settings.groupingField) || '',
}, {
// Increment the count by `incrementBy`.
$inc: { count: settings.incrementBy }
}, {
// new:true specifies that the callback should get the counter AFTER it is updated (incremented).
new: true,
}).exec().then(function setCount(updatedIdentityCounter) {
var count = updatedIdentityCounter.count;
// if an output filter was provided, apply it.
if (typeof settings.outputFilter === 'function') {
count = settings.outputFilter(updatedIdentityCounter.count);
}
// If there are no errors then go ahead and set the document's field to the current count.
doc.set(settings.field, count);
doc.__maiRanOnce = true;
});
}
}).then(next).catch(function(err) {
if (err.name === 'MongoError' && err.code === 11000) {
setTimeout(save, 5);
} else {
next(err);
}
});
})();
// If the document does not have the field we're interested in or that field isn't a number AND the user did
// not specify that we should increment on updates, then just continue the save without any increment logic.
} else {
next();
}
});
// Declare a function to get the next counter for the model/schema.
function nextCount() {
var groupingFieldValue = '';
var callback;
if (typeof arguments[0] !== 'function') {
groupingFieldValue = arguments[0].toString();
callback = arguments[1];
} else {
callback = arguments[0];
}
IdentityCounter.findOne({
model: settings.model,
field: settings.field,
groupingField: groupingFieldValue
}, function (err, counter) {
if (err) { return callback(err); }
callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy);
});
}
// Declare a function to reset counter at the start value - increment value.
function resetCount() {
var groupingFieldValue = '';
var callback;
if (typeof arguments[0] !== 'function') {
groupingFieldValue = arguments[0].toString();
callback = arguments[1];
} else {
callback = arguments[0];
}
IdentityCounter.findOneAndUpdate(
{ model: settings.model, field: settings.field, groupingField: groupingFieldValue },
{ count: settings.startAt - settings.incrementBy },
{ new: true }, // new: true specifies that the callback should get the updated counter.
function (err) {
if (err) return callback(err);
callback(null, settings.startAt);
}
);
}
};