UNPKG

19.2 kBJavaScriptView Raw
1// Generated by CoffeeScript 1.4.0
2(function() {
3 "use strict";
4
5 var async, getKeys, massage, massageCore, massageOne, massaged, preprocFilter, propagate, tools, _,
6 __slice = [].slice;
7
8 async = require('async');
9
10 _ = require('underscore');
11
12 tools = require('./manikin-tools');
13
14 preprocFilter = function(filter) {
15 var x;
16 x = _.extend({}, filter);
17 if (x.id) {
18 x._id = x.id;
19 }
20 delete x.id;
21 return x;
22 };
23
24 massageOne = function(x) {
25 if (!(x != null)) {
26 return x;
27 }
28 x.id = x._id;
29 delete x._id;
30 return x;
31 };
32
33 massageCore = function(r2) {
34 if (Array.isArray(r2)) {
35 return r2.map(massageOne);
36 } else {
37 return massageOne(r2);
38 }
39 };
40
41 massage = function(r2) {
42 return massageCore(JSON.parse(JSON.stringify(r2)));
43 };
44
45 massaged = function(f) {
46 return function(err, data) {
47 if (err) {
48 return f(err);
49 } else {
50 return f(null, massage(data));
51 }
52 };
53 };
54
55 propagate = function(callback, f) {
56 return function() {
57 var args, err;
58 err = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
59 if (err) {
60 return callback(err);
61 } else {
62 return f.apply(this, args);
63 }
64 };
65 };
66
67 getKeys = function(data, target, prefix) {
68 var valids;
69 if (target == null) {
70 target = [];
71 }
72 if (prefix == null) {
73 prefix = '';
74 }
75 valids = ['Array', 'String', 'Boolean', 'Date', 'Number', 'Null'];
76 Object.keys(data).forEach(function(key) {
77 if (valids.some(function(x) {
78 return _(data[key])['is' + x]();
79 })) {
80 return target.push(prefix + key);
81 } else {
82 return getKeys(data[key], target, prefix + key + '.');
83 }
84 });
85 return target;
86 };
87
88 exports.create = function() {
89 var Mixed, ObjectID, ObjectId, Schema, api, defModels, getMeta, insertOps, internalListSub, key, makeModel, metaData, models, mongoose, nullablesValidation, preRemoveCascadeNonNullable, preRemoveCascadeNullable, specTransform, specmodels;
90 if (process.env.NODE_ENV !== 'production') {
91 for (key in require.cache) {
92 delete require.cache[key];
93 }
94 }
95 mongoose = require('mongoose');
96 Schema = mongoose.Schema;
97 Mixed = mongoose.Schema.Types.Mixed;
98 ObjectID = mongoose.mongo.ObjectID;
99 ObjectId = mongoose.Schema.ObjectId;
100 api = {};
101 models = {};
102 specmodels = {};
103 metaData = null;
104 makeModel = function(connection, name, schema) {
105 var ss;
106 ss = new Schema(schema, {
107 strict: true
108 });
109 ss.set('versionKey', false);
110 return connection.model(name, ss, name);
111 };
112 getMeta = function(modelName) {
113 return metaData[modelName];
114 };
115 nullablesValidation = function(schema) {
116 return function(next) {
117 var nonNullOuters, outers, paths, self;
118 self = this;
119 paths = schema.paths;
120 outers = Object.keys(paths).filter(function(x) {
121 return paths[x].options.type === ObjectId && typeof paths[x].options.ref === 'string' && !paths[x].options['x-owner'];
122 }).map(function(x) {
123 return {
124 plur: paths[x].options.ref,
125 sing: x,
126 validation: paths[x].options['x-validation']
127 };
128 });
129 nonNullOuters = outers.filter(function(x) {
130 return self[x.sing] != null;
131 });
132 return async.forEach(nonNullOuters, function(o, callback) {
133 return api.getOne(o.plur, {
134 id: self[o.sing]
135 }, function(err, data) {
136 if (err || !data) {
137 return callback(new Error("Invalid pointer"));
138 } else if (o.validation) {
139 return o.validation(self, data, function(err) {
140 return callback(err ? new Error(err) : void 0);
141 });
142 } else {
143 return callback();
144 }
145 });
146 }, next);
147 };
148 };
149 internalListSub = function(model, outer, id, filter, callback) {
150 var finalFilter;
151 if ((filter[outer] != null) && filter[outer].toString() !== id.toString()) {
152 callback(new Error('No such id'));
153 return;
154 }
155 filter = preprocFilter(filter);
156 finalFilter = _.extend({}, filter, _.object([[outer, id]]));
157 return models[model].find(finalFilter, callback);
158 };
159 preRemoveCascadeNonNullable = function(owner, id, next) {
160 var manys;
161 manys = getMeta(owner.modelName).manyToMany;
162 return async.forEach(manys, function(many, callback) {
163 var obj;
164 obj = _.object([[many.inverseName, id]]);
165 return models[many.ref].update(obj, {
166 $pull: obj
167 }, callback);
168 }, function(err) {
169 var flattenedModels;
170 if (err) {
171 return next(err);
172 }
173 flattenedModels = getMeta(owner.modelName).owns;
174 return async.forEach(flattenedModels, function(mod, callback) {
175 return internalListSub(mod.name, mod.field, id, {}, propagate(callback, function(data) {
176 return async.forEach(data, function(item, callback) {
177 return item.remove(callback);
178 }, callback);
179 }));
180 }, next);
181 });
182 };
183 preRemoveCascadeNullable = function(owner, id, next) {
184 var flattenedModels, ownedModels;
185 ownedModels = Object.keys(models).map(function(modelName) {
186 var paths;
187 paths = models[modelName].schema.paths;
188 return Object.keys(paths).filter(function(x) {
189 return paths[x].options.type === ObjectId && paths[x].options.ref === owner.modelName && !paths[x].options['x-owner'];
190 }).map(function(x) {
191 return {
192 name: modelName,
193 field: x
194 };
195 });
196 });
197 flattenedModels = _.flatten(ownedModels);
198 return async.forEach(flattenedModels, function(mod, callback) {
199 return internalListSub(mod.name, mod.field, id, {}, propagate(callback, function(data) {
200 return async.forEach(data, function(item, callback) {
201 item[mod.field] = null;
202 item.save();
203 return callback();
204 }, callback);
205 }));
206 }, next);
207 };
208 specTransform = function(allspec, modelName, tgt, src, keys) {
209 return keys.forEach(function(key) {
210 if (src[key].type === 'mixed') {
211 return tgt[key] = {
212 type: Mixed
213 };
214 } else if (src[key].type === 'nested') {
215 tgt[key] = {};
216 return specTransform(allspec, modelName, tgt[key], src[key], _.without(Object.keys(src[key]), 'type'));
217 } else if (src[key].type === 'string') {
218 tgt[key] = _.extend({}, src[key], {
219 type: String
220 });
221 if (src[key].validate != null) {
222 return tgt[key].validate = function(value, callback) {
223 return src[key].validate(api, value, callback);
224 };
225 }
226 } else if (src[key].type === 'number') {
227 return tgt[key] = _.extend({}, src[key], {
228 type: Number
229 });
230 } else if (src[key].type === 'date') {
231 return tgt[key] = _.extend({}, src[key], {
232 type: Date
233 });
234 } else if (src[key].type === 'boolean') {
235 return tgt[key] = _.extend({}, src[key], {
236 type: Boolean
237 });
238 } else if (src[key].type === 'hasOne') {
239 return tgt[key] = {
240 ref: src[key].model,
241 'x-validation': src[key].validation
242 };
243 } else if (src[key].type === 'hasMany') {
244 tgt[key] = [
245 {
246 type: ObjectId,
247 ref: src[key].model,
248 inverseName: src[key].inverseName
249 }
250 ];
251 return allspec[src[key].model][src[key].inverseName] = [
252 {
253 type: ObjectId,
254 ref: modelName,
255 inverseName: key
256 }
257 ];
258 }
259 });
260 };
261 defModels = function(models) {
262 var allspec, newrest, toDef;
263 specmodels = tools.desugar(models);
264 toDef = [];
265 newrest = {};
266 allspec = {};
267 Object.keys(specmodels).forEach(function(modelName) {
268 return allspec[modelName] = {};
269 });
270 Object.keys(specmodels).forEach(function(modelName) {
271 var inspec, owners, spec;
272 spec = allspec[modelName];
273 owners = specmodels[modelName].owners || {};
274 inspec = specmodels[modelName].fields || {};
275 specTransform(allspec, modelName, spec, inspec, Object.keys(inspec));
276 return newrest[modelName] = _.extend({}, specmodels[modelName], {
277 fields: spec
278 });
279 });
280 Object.keys(newrest).forEach(function(modelName) {
281 var conf;
282 conf = newrest[modelName];
283 Object.keys(conf.owners).forEach(function(ownerName) {
284 return conf.fields[ownerName] = {
285 type: ObjectId,
286 ref: conf.owners[ownerName],
287 required: true,
288 'x-owner': true
289 };
290 });
291 Object.keys(conf.indirectOwners).forEach(function(p) {
292 return conf.fields[p] = {
293 type: ObjectId,
294 ref: conf.indirectOwners[p],
295 required: true,
296 'x-indirect-owner': true
297 };
298 });
299 Object.keys(conf.fields).forEach(function(fieldName) {
300 if (conf.fields[fieldName].ref != null) {
301 return conf.fields[fieldName].type = ObjectId;
302 }
303 });
304 return toDef.push([modelName, newrest[modelName]]);
305 });
306 metaData = tools.getMeta(specmodels);
307 return toDef;
308 };
309 (function() {
310 var connection;
311 connection = null;
312 api.connect = function(databaseUrl, inputModels, callback) {
313 try {
314 connection = mongoose.createConnection(databaseUrl);
315 defModels(inputModels).forEach(function(_arg) {
316 var name, v;
317 name = _arg[0], v = _arg[1];
318 models[name] = makeModel(connection, name, v.fields);
319 models[name].schema.pre('save', nullablesValidation(models[name].schema));
320 models[name].schema.pre('remove', function(next) {
321 return preRemoveCascadeNonNullable(models[name], this._id.toString(), next);
322 });
323 return models[name].schema.pre('remove', function(next) {
324 return preRemoveCascadeNullable(models[name], this._id.toString(), next);
325 });
326 });
327 } catch (ex) {
328 callback(ex);
329 return;
330 }
331 return callback();
332 };
333 return api.close = function(callback) {
334 connection.close();
335 return callback();
336 };
337 })();
338 api.post = function(model, indata, callback) {
339 var owners, ownersOwners, ownersRaw, saveFunc;
340 saveFunc = function(data) {
341 return new models[model](data).save(function(err) {
342 var fieldMatch, valueMatch;
343 if (err && err.code === 11000) {
344 fieldMatch = err.err.match(/([a-zA-Z]+)_1/);
345 valueMatch = err.err.match(/"([a-zA-Z]+)"/);
346 if (fieldMatch && valueMatch) {
347 return callback(new Error("Duplicate value '" + valueMatch[1] + "' for " + fieldMatch[1]));
348 } else {
349 return callback(new Error("Unique constraint violated"));
350 }
351 } else {
352 return massaged(callback).apply(this, arguments);
353 }
354 });
355 };
356 ownersRaw = getMeta(model).owners;
357 owners = _(ownersRaw).pluck('plur');
358 ownersOwners = _.flatten(owners.map(function(x) {
359 return getMeta(x).owners;
360 }));
361 if (ownersOwners.length === 0) {
362 return saveFunc(indata);
363 } else {
364 return api.getOne(owners[0], {
365 filter: {
366 id: indata[ownersRaw[0].sing]
367 }
368 }, propagate(callback, function(ownerdata) {
369 var metaFields, paths;
370 paths = models[owners[0]].schema.paths;
371 metaFields = Object.keys(paths).filter(function(key) {
372 return !!paths[key].options['x-owner'] || !!paths[key].options['x-indirect-owner'];
373 });
374 metaFields.forEach(function(key) {
375 return indata[key] = ownerdata[key];
376 });
377 return saveFunc(indata);
378 }));
379 }
380 };
381 api.list = function(model, filter, callback) {
382 var defaultSort, rr;
383 filter = preprocFilter(filter);
384 defaultSort = specmodels[model].defaultSort;
385 rr = models[model].find(filter);
386 if (defaultSort != null) {
387 rr = rr.sort(_.object([[defaultSort, 'asc']]));
388 }
389 return rr.exec(massaged(callback));
390 };
391 api.getOne = function(model, config, callback) {
392 var filter;
393 filter = preprocFilter(config.filter || {});
394 return models[model].findOne(filter, function(err, data) {
395 if (err) {
396 if (err.toString() === 'Error: Invalid ObjectId') {
397 callback(new Error('No such id'));
398 } else {
399 callback(err);
400 }
401 } else if (!(data != null)) {
402 return callback(new Error('No match'));
403 } else {
404 return callback(null, massage(data));
405 }
406 });
407 };
408 api.delOne = function(model, filter, callback) {
409 filter = preprocFilter(filter);
410 return models[model].findOne(filter, function(err, d) {
411 if (err) {
412 if (err.toString() === 'Error: Invalid ObjectId') {
413 return callback(new Error('No such id'));
414 } else {
415 return callback(err);
416 }
417 } else if (!(d != null)) {
418 return callback(new Error('No such id'));
419 } else {
420 return d.remove(function(err) {
421 return callback(err, !err ? massage(d) : void 0);
422 });
423 }
424 });
425 };
426 api.putOne = function(modelName, data, filter, callback) {
427 var inputFields, inputFieldsValid, invalidFields, model, validField;
428 filter = preprocFilter(filter);
429 model = models[modelName];
430 inputFieldsValid = getKeys(data);
431 inputFields = Object.keys(data);
432 validField = Object.keys(model.schema.paths);
433 invalidFields = _.difference(inputFieldsValid, validField);
434 if (invalidFields.length > 0) {
435 callback(new Error("Invalid fields: " + invalidFields.join(', ')));
436 return;
437 }
438 return model.findOne(filter, function(err, d) {
439 if (err != null) {
440 if (err.message === 'Invalid ObjectId') {
441 callback(new Error("No such id"));
442 } else {
443 callback(err);
444 }
445 return;
446 }
447 if (!(d != null)) {
448 callback(new Error("No such id"));
449 return;
450 }
451 inputFields.forEach(function(key) {
452 return d[key] = data[key];
453 });
454 return d.save(function(err) {
455 return callback(err, err ? null : massage(d));
456 });
457 });
458 };
459 api.delMany = function(primaryModel, primaryId, propertyName, secondaryId, callback) {
460 var inverseName, mm, secondaryModel;
461 mm = getMeta(primaryModel).manyToMany.filter(function(x) {
462 return x.name === propertyName;
463 })[0];
464 if (!(mm != null)) {
465 callback(new Error('Invalid many-to-many property'));
466 return;
467 }
468 secondaryModel = mm.ref;
469 inverseName = mm.inverseName;
470 return async.forEach([
471 {
472 model: primaryModel,
473 id: primaryId,
474 property: propertyName,
475 secondaryId: secondaryId
476 }, {
477 model: secondaryModel,
478 id: secondaryId,
479 property: inverseName,
480 secondaryId: primaryId
481 }
482 ], function(item, callback) {
483 return models[item.model].findById(item.id, propagate(callback, function(data) {
484 var conditions, options, update;
485 conditions = {
486 _id: item.id
487 };
488 update = {
489 $pull: _.object([[item.property, item.secondaryId]])
490 };
491 options = {};
492 return models[item.model].update(conditions, update, options, function(err, numAffected) {
493 return callback(err);
494 });
495 }));
496 }, callback);
497 };
498 insertOps = [];
499 api.postMany = function(primaryModel, primaryId, propertyName, secondaryId, callback) {
500 var hasAlready, insertOpMatch, insertOpNow, inverseName, mm, secondaryModel;
501 mm = getMeta(primaryModel).manyToMany.filter(function(x) {
502 return x.name === propertyName;
503 })[0];
504 if (!(mm != null)) {
505 callback(new Error('Invalid many-to-many property'));
506 return;
507 }
508 secondaryModel = mm.ref;
509 inverseName = mm.inverseName;
510 insertOpNow = [
511 {
512 primaryModel: primaryModel,
513 primaryId: primaryId,
514 propertyName: propertyName,
515 secondaryId: secondaryId
516 }, {
517 primaryModel: secondaryModel,
518 primaryId: secondaryId,
519 propertyName: inverseName,
520 secondaryId: primaryId
521 }
522 ];
523 insertOpMatch = function(x1, x2) {
524 return x1.primaryModel === x2.primaryModel && x1.primaryId === x2.primaryId && x1.propertyName === x2.propertyName && x1.secondaryId === x2.secondaryId;
525 };
526 hasAlready = insertOps.some(function(x) {
527 return insertOpNow.some(function(y) {
528 return insertOpMatch(x, y);
529 });
530 });
531 if (hasAlready) {
532 callback(null, {
533 status: 'insert already in progress'
534 });
535 return;
536 }
537 insertOpNow.forEach(function(op) {
538 return insertOps.push(op);
539 });
540 return async.map(insertOpNow, function(item, callback) {
541 return models[item.primaryModel].findById(item.primaryId, callback);
542 }, propagate(callback, function(datas) {
543 var updated;
544 updated = [false, false];
545 insertOpNow.forEach(function(conf, i) {
546 if (-1 === datas[i][conf.propertyName].indexOf(conf.secondaryId)) {
547 datas[i][conf.propertyName].push(conf.secondaryId);
548 return updated[i] = true;
549 }
550 });
551 return async.forEach([0, 1], function(index, callback) {
552 if (updated[index]) {
553 return datas[index].save(callback);
554 } else {
555 return callback();
556 }
557 }, function(err) {
558 if (err) {
559 return callback(err);
560 }
561 insertOps = insertOps.filter(function(x) {
562 return !_(insertOpNow).contains(x);
563 });
564 return callback(null, {
565 status: (updated.some(function(x) {
566 return x;
567 }) ? 'inserted' : 'already inserted')
568 });
569 });
570 }));
571 };
572 api.getMany = function(primaryModel, primaryId, propertyName, callback) {
573 return models[primaryModel].findOne({
574 _id: primaryId
575 }).populate(propertyName).exec(function(err, story) {
576 return callback(err, massage(story[propertyName]));
577 });
578 };
579 return api;
580 };
581
582}).call(this);