base-domain
Version:
simple module to help build Domain-Driven Design
353 lines (308 loc) • 9.4 kB
JavaScript
'use strict';
var DomainError, EntityPool, FixtureLoader, Scope, Util, debug,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
EntityPool = require('./entity-pool');
DomainError = require('./lib/domain-error');
Util = require('./util');
debug = require('debug')('base-domain:fixture-loader');
/**
Load fixture data (only works in Node.js)
@class FixtureLoader
@module base-domain
*/
FixtureLoader = (function() {
function FixtureLoader(facade, fixtureDirs) {
this.facade = facade;
this.fixtureDirs = fixtureDirs != null ? fixtureDirs : [];
if (!Array.isArray(this.fixtureDirs)) {
this.fixtureDirs = [this.fixtureDirs];
}
this.entityPool = new EntityPool;
this.fixturesByModel = {};
}
/**
@method load
@public
@param {Object} [options]
@param {Boolean} [options.async] if true, returns Promise.
@return {EntityPool|Promise(EntityPool)}
*/
FixtureLoader.prototype.load = function(options) {
var e, ext, file, fixtureDir, fs, fx, j, k, l, len, len1, len2, modelName, modelNames, names, path, ref, ref1, ref2, ref3, ref4, requireFile;
if (options == null) {
options = {};
}
ref = this.facade.constructor, fs = ref.fs, requireFile = ref.requireFile;
try {
modelNames = [];
ref1 = this.fixtureDirs;
for (j = 0, len = ref1.length; j < len; j++) {
fixtureDir = ref1[j];
ref2 = fs.readdirSync(fixtureDir + '/data');
for (k = 0, len1 = ref2.length; k < len1; k++) {
file = ref2[k];
ref3 = file.split('.'), modelName = ref3[0], ext = ref3[1];
if (ext !== 'coffee' && ext !== 'js' && ext !== 'json') {
continue;
}
path = fixtureDir + '/data/' + file;
fx = requireFile(path);
fx.path = path;
fx.fixtureDir = fixtureDir;
this.fixturesByModel[modelName] = fx;
modelNames.push(modelName);
}
}
modelNames = this.topoSort(modelNames);
names = (ref4 = options.names) != null ? ref4 : modelNames;
modelNames = modelNames.filter(function(name) {
return indexOf.call(names, name) >= 0;
});
if (options.async) {
return this.saveAsync(modelNames).then((function(_this) {
return function() {
return _this.entityPool;
};
})(this));
} else {
for (l = 0, len2 = modelNames.length; l < len2; l++) {
modelName = modelNames[l];
this.loadAndSaveModels(modelName);
}
return this.entityPool;
}
} catch (error) {
e = error;
if (options.async) {
return Promise.reject(e);
} else {
throw e;
}
}
};
/**
@private
*/
FixtureLoader.prototype.saveAsync = function(modelNames) {
var modelName;
if (!modelNames.length) {
return Promise.resolve(true);
}
modelName = modelNames.shift();
return Promise.resolve(this.loadAndSaveModels(modelName)).then((function(_this) {
return function() {
return _this.saveAsync(modelNames);
};
})(this));
};
/**
@private
*/
FixtureLoader.prototype.loadAndSaveModels = function(modelName) {
var PORTION_SIZE, data, e, fx, ids, repo, saveModelsByPortion;
fx = this.fixturesByModel[modelName];
data = (function() {
switch (typeof fx.data) {
case 'string':
return this.readTSV(fx.fixtureDir, fx.data);
case 'function':
return fx.data.call(new Scope(this, fx), this.entityPool);
case 'object':
return fx.data;
}
}).call(this);
try {
repo = this.facade.createPreferredRepository(modelName);
} catch (error) {
e = error;
console.error(e.message);
return;
}
if (data == null) {
throw new Error("Invalid fixture in model '" + modelName + "'. Check the fixture file: " + fx.path);
}
ids = Object.keys(data);
debug('inserting %s models into %s', ids.length, modelName);
PORTION_SIZE = 5;
return (saveModelsByPortion = (function(_this) {
return function() {
var id, idsPortion, obj, results;
if (ids.length === 0) {
return;
}
idsPortion = ids.slice(0, PORTION_SIZE);
ids = ids.slice(idsPortion.length);
results = (function() {
var j, len, results1;
results1 = [];
for (j = 0, len = idsPortion.length; j < len; j++) {
id = idsPortion[j];
obj = data[id];
obj.id = id;
results1.push(this.saveModel(repo, obj));
}
return results1;
}).call(_this);
if (Util.isPromise(results[0])) {
return Promise.all(results).then(function() {
return saveModelsByPortion();
});
} else {
return saveModelsByPortion();
}
};
})(this))();
};
FixtureLoader.prototype.saveModel = function(repo, obj) {
var result;
result = repo.save(obj, {
method: 'create',
fixtureInsertion: true,
include: {
entityPool: this.entityPool
}
});
if (Util.isPromise(result)) {
return result.then((function(_this) {
return function(entity) {
return _this.entityPool.set(entity);
};
})(this));
} else {
return this.entityPool.set(result);
}
};
/**
topological sort
@method topoSort
@private
*/
FixtureLoader.prototype.topoSort = function(names) {
var add, el, j, k, len, len1, namesWithDependencies, sortedNames, visit, visited;
namesWithDependencies = [];
for (j = 0, len = names.length; j < len; j++) {
el = names[j];
(add = (function(_this) {
return function(name) {
var depname, fx, k, len1, ref, ref1, results1;
if (indexOf.call(namesWithDependencies, name) >= 0) {
return;
}
namesWithDependencies.push(name);
fx = _this.fixturesByModel[name];
if (fx == null) {
throw new DomainError('base-domain:modelNotFound', "model '" + name + "' is not found. It might be written in some 'dependencies' property.");
}
ref1 = (ref = fx.dependencies) != null ? ref : [];
results1 = [];
for (k = 0, len1 = ref1.length; k < len1; k++) {
depname = ref1[k];
results1.push(add(depname));
}
return results1;
};
})(this))(el);
}
visited = {};
sortedNames = [];
for (k = 0, len1 = namesWithDependencies.length; k < len1; k++) {
el = namesWithDependencies[k];
(visit = (function(_this) {
return function(name, ancestors) {
var depname, fx, l, len2, ref, ref1;
fx = _this.fixturesByModel[name];
if (visited[name] != null) {
return;
}
ancestors.push(name);
visited[name] = true;
ref1 = (ref = fx.dependencies) != null ? ref : [];
for (l = 0, len2 = ref1.length; l < len2; l++) {
depname = ref1[l];
if (indexOf.call(ancestors, depname) >= 0) {
throw new DomainError('base-domain:dependencyLoop', 'dependency chain is making loop');
}
visit(depname, ancestors.slice());
}
return sortedNames.push(name);
};
})(this))(el, []);
}
return sortedNames;
};
/*
read TSV, returns model data
@method readTSV
@private
*/
FixtureLoader.prototype.readTSVContent = function(txt) {
var csvParse, data, i, id, j, k, len, len1, name, names, obj, objs, tsv, value;
csvParse = this.facade.constructor.csvParse;
tsv = csvParse(txt, {
delimiter: '\t'
});
names = tsv.shift();
names.shift();
objs = {};
for (j = 0, len = tsv.length; j < len; j++) {
data = tsv[j];
obj = {};
id = data.shift();
obj.id = id;
if (!id) {
break;
}
for (i = k = 0, len1 = names.length; k < len1; i = ++k) {
name = names[i];
if (!name) {
break;
}
value = data[i];
if (value.match(/^[0-9]+$/)) {
value = Number(value);
}
obj[name] = value;
}
objs[obj.id] = obj;
}
return objs;
};
/**
read TSV, returns model data
@method readTSV
@private
*/
FixtureLoader.prototype.readTSV = function(fixtureDir, file) {
var fs;
fs = this.facade.constructor.fs;
return this.readTSVContent(fs.readFileSync(fixtureDir + '/tsvs/' + file, 'utf8'));
};
return FixtureLoader;
})();
/**
'this' property in fixture's data function
this.readTSV('xxx.tsv') is available
module.exports = {
data: function(entityPool) {
this.readTSV('model-name.tsv');
}
};
@class Scope
@private
*/
Scope = (function() {
function Scope(loader, fx1) {
this.loader = loader;
this.fx = fx1;
}
/**
@method readTSV
@param {String} filename filename (directory is automatically set)
@return {Object} tsv contents
*/
Scope.prototype.readTSV = function(filename) {
return this.loader.readTSV(this.fx.fixtureDir, filename);
};
return Scope;
})();
module.exports = FixtureLoader;