-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdb.js
More file actions
356 lines (291 loc) · 9.14 KB
/
db.js
File metadata and controls
356 lines (291 loc) · 9.14 KB
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
var mongo = require('mongodb'),
uuid = require('./util/uuid'),
EventEmitter = require('events').EventEmitter;
///////////////////////////////
// Utility
///////////////////////////////
var makeId = function(collection, callback) {
return collection + '/' + uuid.uuid4();
}
var niceIds = {};
var makeNiceId = function(collection, callback) {
// If we already have a nice ID stored for this collection, return
// it as a string, and increment it.
if (niceIds.hasOwnProperty(collection)) return callback(collection + '/' + (niceIds[collection]++));
// Otherwise, we just need to count the number of items in the
// collection and initialize the nice id counter to that value.
// Grab the collection from the db
db.collection(collection, function(err, col) {
// On an error, fail horribly.
if (err) {
console.log('Unable to open collection ' + collection + ':');
console.log(err.stack);
console.log('');
process.exit();
}
// Count the number of objects in the collection to generate
// the friendly ID.
col.count(function(err, n) {
// On an error, fail horribly.
if (err) {
console.log('Unable to count objects in collection ' + collection + ':');
console.log(err.stack);
console.log('');
process.exit();
}
// Set the nice id counter
niceIds[collection] = n + 1; //do 1-based counting
// Execute makeNiceId again, which will result in it using the
// cached version to return the id to the caller. This keeps
// that logic in one place.
makeNiceId(collection, callback);
});
});
};
var ensureIndex = function(collection, field, type) {
var s = {};
s[field] = type;
db.ensureIndex(collection, s, function(err) {
if (err) {
console.log('Unable to ensure index on collection ' + collection + ':' + field);
console.log(err.stack);
console.log('');
return;
}
});
};
///////////////////////////////
// DB Stuff
///////////////////////////////
var db;
var init = function(host, port, name, callback) {
db = new mongo.Db(name, new mongo.Server(host, port, {}));
db.open(function(err, p_client) {
// If we get an error opening the database, we need to fail
// hard because there's nothing else to do if we can't read/store
// data.
if (err) {
console.log(' Unable to open database connection!');
process.exit();
} else {
// If the DB name is "test", we automatically clear the DB when
// we connect.
if (name == 'test') {
console.log(' Dropping database...');
db.dropDatabase(function(err) {
// Loudly continue one rror
if (err)
console.log(' Unable to drop database');
callback();
});
} else {
callback();
}
}
});
};
var apply = function() {
// Checking for zero arguments here makes things easier down the
// road.
if (!arguments.length) return;
// Shift out the callback if it's there
var callback;
if (typeof arguments[arguments.length - 1] == 'function')
callback = arguments[--arguments.length];
// Countdown used for determining when to fire the callback
var opCount = arguments.length;
//process each fieldset
for (var i=0; i<arguments.length; i++) {
var fs = arguments[i];
//event type
var eventType = !fs._id ? 'create' : fs.deleted ? 'delete' : 'update';
//set the updated date
fs.modified = new Date();
//perform the upsert
var upsert = function() {
db.collection(fs.getCollection(), function(err, col) {
//ye olde error dump
if (err) return console.log(err.stack, '');
// This is where it gets a little funky. Upserting with an
// id doesn't work for update. So we clone the FS, remove
// the id, and use it for the query.
var nfs = fs.clone();
var nid = fs._id;
delete nfs._id;
col.update({_id: nid}, {'$set': nfs}, {upsert: true, safe: true}, function(err) {
//ye olde error dump
if (err) return console.log(err.stack, '');
//send the event, with the cloned fieldset so event
//handlers can't clobber the original
if (!err)
events.emit(eventType, fs.clone());
// If all the inserts are done, fire the callback
if (--opCount == 0 && callback) callback();
});
});
};
// If there's no ID, it's a new record and we need to set up
// the default fields and create an id.
if (!fs._id)
//generate an id for it
fs.bootstrap().genId(upsert);
else
upsert();
}
};
var get = function(fs, callback) {
// If there's no ID we can't get the object
if (!fs._id) {
console.log('Attempting to call get on a fieldset with no _id');
return callback(true);
}
// Grab the appropriate collection
db.collection(fs.getCollection(), function(err, col) {
// Log errors and pass them on down
if (err) {
console.log(err.stack);
return callback(true);
}
// Get the object from the collection
col.find({_id: fs._id}).limit(1).nextObject(function(err, obj) {
// Log errors and pass them on down
if (err) {
console.log(err.stack)
return callback(true);
}
// If the object is null, return with an error
if (!obj)
return callback(false, false);
// Fix the fields
fs.merge(obj);
callback(false, true);
});
});
};
var queryOne = function(type, q, callback) {
q.deleted = false;
if (!type.prototype.getCollection) console.log('Type is not a fieldset class') || callback(true);
else db.collection(type.prototype.getCollection(), function(err, col) {
if (err) console.log(err.stack) || callback(true);
else col.find(q).limit(1).nextObject(function(err, obj) {
if (err) console.log(err.stack) || callback(true);
else callback(false, obj && new type().merge(obj));
});
});
};
/* type, q, [[offset, limit]], [sort], callback */
var query = function(type, q) {
// Magic args
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var offset = 0;
var limit = 0;
if (args.length > 2) {
offset = offset || args[2][0];
limit = limit || args[2][1];
}
var sort = undefined;
if (args.length > 3)
sort = args[3] || undefined;
// Don't query deleted fields
q.deleted = {$ne: true};
var typeName = '';
if (typeof type == 'string') {
throw new Error('Must supply a FieldSet instance as the type');
} else if (type && type.prototype && type.prototype instanceof FieldSet) {
typeName = type.prototype.getCollection();
var genType = function() { return new type(); };
} else {
console.log('Type is not a FieldSet or String');
console.log('type: ', type);
console.log('');
throw new Error('Type must be a FieldSet or String');
}
db.collection(typeName, {}, function(err, col) {
if (err) {
console.log(err.stack);
console.log('');
return callback(err);
}
// Prepare query options.
var options = {};
// Offset
if (offset) options.skip = offset;
if (limit) options.limit = limit;
// Sort
if (sort) {
if (sort[0] == '-')
options.sort = [[sort.substr(1), 'desc']];
else if (sort[0] == '+')
options.sort = [[sort.substr(1), 'asc']];
else
options.sort = [[sort, 'asc']];
}
// Base query
var f = col.find(q, options);
// Run the query and fetch the individual things
f.toArray(function(err, objs) {
if (err) {
console.log(err.stack || err)
return callback(err);
}
var fss = [];
for (var i=0; i<objs.length; i++)
fss.push(genType().merge(objs[i]));
callback(false, fss);
});
});
};
///////////////////////////////
// FieldSets
///////////////////////////////
var FieldSet = function(collection) {
//require collection
if (!collection)
throw new Error('Must set collection name when creating a fieldset');
this.getCollection = function() {
return collection;
};
};
FieldSet.prototype.genId = function(callback) {
this._id = makeId(this.getCollection());
if (callback) callback();
return this;
};
FieldSet.prototype.clone = function() {
//create the new fieldset
var fs = new this.constructor();
//copy the fields over
for (var i in this) if (this.hasOwnProperty(i))
fs[i] = this[i];
//and return it
return fs;
};
FieldSet.prototype.merge = function(from) {
for (var i in from) if (from.hasOwnProperty(i))
this[i] = from[i];
return this;
};
FieldSet.prototype.bootstrap = function(id) {
this._id = id;
this.created = this.modified || new Date();
this.deleted = false;
return this;
};
///////////////////////////////
// Exports
///////////////////////////////
exports.init = init;
exports.apply = apply;
exports.get = get;
exports.queryOne = queryOne;
exports.query = query;
exports.makeNiceId = makeNiceId;
exports.ensureIndex = ensureIndex;
exports.FieldSet = FieldSet;
///////////////////////////////
// Events Hack
///////////////////////////////
var events = new EventEmitter();
events.setMaxListeners(0); // TO THE MOOOOOON
exports.events = events;