forked from nassor/mongoose-auto-increment
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
186 lines (169 loc) · 6.88 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
// Module Scope
var mongoose = require('mongoose'),
extend = require('extend'),
counterSchema,
IdentityCounter;
// Initialize plugin by creating counter collection in database.
exports.initialize = function (connection) {
try {
IdentityCounter = connection.model('IdentityCounter');
} catch (ex) {
if (ex.name === 'MissingSchemaError') {
// Create new counter schema.
counterSchema = new mongoose.Schema({
model: { type: String, require: true },
field: { type: String, require: true },
count: { type: Number, default: 0 }
});
// Create a unique index using the "field" and "model" fields.
counterSchema.index({ field: 1, model: 1 }, {
unique: true,
required: true,
index: -1,
sparse: true,
});
// 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 = async function (schema, options) {
// 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");
// Default settings and plugin scope variables.
var settings = {
model: null, // The model to configure the plugin for.
field: '_id', // The field the plugin should track.
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,
},
fields = {}, // A hash of fields to add properties to in Mongoose.
ready = false; // True if the counter collection has been updated and the document is ready to be saved.
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':
extend(settings, options);
break;
}
if (settings.model == null)
throw new Error("model must be set");
// Add properties for field in schema.
fields[settings.field] = {
type: Number,
require: true,
sparse: true,
};
if (settings.field !== '_id')
fields[settings.field].unique = settings.unique
schema.add(fields);
// Find the counter for this model and the relevant field.
IdentityCounter.findOne({ model: settings.model, field: settings.field })
.then((counter) => {
if (!counter) {
// If no counter exists then create one and save it.
counter = new IdentityCounter({ model: settings.model, field: settings.field, count: settings.startAt - settings.incrementBy });
return counter.save().then(() => {
ready = true;
});
} else {
ready = true;
}
})
.catch((err) => {
// Handle errors here
console.error(err);
});
// Declare a function to get the next counter for the model/schema.
var nextCount = async function (callback) {
try {
const counter = await IdentityCounter.findOne({
model: settings.model,
field: settings.field
});
return callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy);
} catch (err){
return callback(err);
}
};
// Add nextCount as both a method on documents and a static on the schema for convenience.
schema.method('nextCount', nextCount);
schema.static('nextCount', nextCount);
// Declare a function to reset counter at the start value - increment value.
var resetCount = async function (callback) {
try {
await IdentityCounter.findOneAndUpdate(
{ model: settings.model, field: settings.field },
{ count: settings.startAt - settings.incrementBy },
{ new: true }); // new: true specifies that the callback should get the updated counter.
return callback(null, settings.startAt);
} catch (err) {
return callback(err);
}
};
// 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('save', function (next) {
// Get reference to the document being saved.
var doc = this;
// Only do this if it is a new document (see http://mongoosejs.com/docs/api.html#document_Document-isNew)
if (doc.isNew) {
// Declare self-invoking save function.
(async function save() {
// If ready, run increment logic.
// Note: ready is true when an existing counter collection is found or after it is created for the
// first time.
if (ready) {
// 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[settings.field] === 'number') {
try {
await IdentityCounter.findOneAndUpdate(
// IdentityCounter documents are identified by the model and field that the plugin was invoked for.
// Check also that count is less than field value.
{ model: settings.model, field: settings.field, count: { $lt: doc[settings.field] } },
// Change the count of the value found to the new field value.
{ count: doc[settings.field] });
next();
} catch (err) {
return next(err);
}
} else {
try {
// Find the counter collection entry for this model and field and update it.
var updatedIdentityCounter = await IdentityCounter.findOneAndUpdate(
// IdentityCounter documents are identified by the model and field that the plugin was invoked for.
{ model: settings.model, field: settings.field },
// 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 });
doc[settings.field] = updatedIdentityCounter.count;
return next();
} catch (err) {
return next(err);
}
}
}
// If not ready then set a 5 millisecond timer and try to save again. It will keep doing this until
// the counter collection is ready.
else
setTimeout(save, 5);
})();
}
// 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();
});
};