UNPKG

independent-juggler

Version:

Use loopback-datasource-juggler standalone

275 lines (234 loc) 9.86 kB
var path = require('path'); var fs = require('fs'); var _ = require('lodash'); var async = require('async'); var Registry = function(juggler, options) { this.dataSources = {}; this.juggler = juggler; this.modelBuilder = new juggler.ModelBuilder(); options = _.extend({}, options); var rootDir = options.dir || process.cwd(); var configDir = options.configDir || path.join(rootDir, 'config'); var modelsDir = options.modelsDir || path.join(rootDir, 'models'); var mixinsDir = options.mixinsDir || path.join(rootDir, 'mixins'); if (options.withinApplication === true) { configDir = options.configDir || path.join(rootDir, 'server'); modelsDir = options.modelsDir || path.join(rootDir, 'common', 'models'); mixinsDir = options.mixinsDir || path.join(rootDir, 'common', 'mixins'); } var env = options.env || process.env.NODE_ENV || 'development'; var datasourceConfig = _.extend({}, options.datasources); var modelConfig = this.modelConfig = _.extend({}, options.models); var datasourceConfigs = findConfigFiles(configDir, env, 'datasources'); mergeConfigFiles(datasourceConfig, datasourceConfigs); var modelConfigs = findConfigFiles(configDir, env, 'model-config'); var resolved = mergeConfigFiles(modelConfig, modelConfigs); var modelSources = [modelsDir].concat(resolved.models); var mixinSources = [mixinsDir].concat(resolved.mixins); if (_.isString(options.configure) || _.isArray(options.configure)) { datasourceConfig = _.pick(datasourceConfig, options.configure); } _.each(datasourceConfig, function(config, name) { config = config || {}; this.dataSources[config.name || name] = this.createDataSource(name, config); }.bind(this)); this.modelDefinitions = this.loadModelDefinitions(modelSources); this.loadMixinDefinitions(mixinSources); }; Registry.prototype.connect = function(callback) { if (this.models) return callback && callback(err, this.models); if (_.isEmpty(this.dataSources)) throw new Error('No DataSources configured'); if (_.isEmpty(this.modelConfig)) throw new Error('No Models configured'); var dataSources = _.groupBy(this.modelDefinitions, 'dataSource'); var models = this.models = {}; _.each(dataSources, function(definitions, dataSource) { var schemas = _.pluck(definitions, 'definition'); _.extend(models, this.defineModels(dataSource, schemas)); }.bind(this)); var attachModel = this.attachModel.bind(this); var applySourceFile = this.applySourceFile.bind(this); var finalizeModel = this.finalizeModel.bind(this); async.each(_.values(this.dataSources), function(ds, next) { ds.connect(next); }.bind(this), function(err) { _.each(models, applySourceFile); _.each(models, attachModel); _.each(models, finalizeModel); callback && callback(err, models); }); }; Registry.prototype.disconnect = function(callback) { var dataSources = this.dataSources; async.each(_.keys(dataSources), function(name, next) { dataSources[name].disconnect(next); }, function(err) { var models = this.models; delete this.models; callback && callback(err, models); }.bind(this)); }; Registry.prototype.setupDataSource = function(name, options) { this.dataSources[name] = this.createDataSource(name, options); return this.dataSources[name]; }; Registry.prototype.createDataSource = function(name, options) { var DataSource = this.juggler.DataSource; return new DataSource(name, options, this.modelBuilder); }; Registry.prototype.loadModelDefinitions = function(sourcePaths) { var modelDefinitions = {}; var props = ['name', 'properties', 'options']; var dataSources = _.keys(this.dataSources); var modelConfig = this.modelConfig || {}; this.modelConfig = _.reduce(modelConfig, function(config, c, modelName) { if (_.include(dataSources, c.dataSource)) { config[modelName] = c; } return config; }, {}); modelConfig = this.modelConfig; _.each(sourcePaths, function(sourceDir) { var files = tryReadDir(sourceDir); _.each(files, function(filename) { var filepath = path.join(sourceDir, filename); var ext = path.extname(filename); var name = path.basename(filename, ext); if (ext === '.json') { var config = require(filepath); var definition = _.pick(config, props); definition.options = _.extend({}, definition.options, _.omit(config, props)); modelDefinitions[name] = modelDefinitions[name] || {}; modelDefinitions[name].definition = definition; var modelName = definition.name || classify(name); var hasConfig = _.isObject(modelConfig[modelName]); var dataSource = hasConfig && modelConfig[modelName].dataSource; if (_.include(dataSources, dataSource)) { modelDefinitions[name].name = modelName; modelDefinitions[name].load = true; modelDefinitions[name].dataSource = dataSource; } else { delete modelDefinitions[name]; } } else if (ext === '.js') { modelDefinitions[name] = modelDefinitions[name] || {}; modelDefinitions[name].sourceFile = filepath; } }); }); var models = {}; _.each(modelDefinitions, function(definition) { if (definition.load) models[definition.name] = definition; }); return models; }; Registry.prototype.loadMixinDefinitions = function(sourcePaths) { var modelBuilder = this.modelBuilder; _.each(sourcePaths, function(sourceDir) { var files = tryReadDir(sourceDir); _.each(files, function(filename) { var filepath = path.join(sourceDir, filename); var ext = path.extname(filename); if (ext === '.js') { var name = classify(path.basename(filename, ext)); var fn = require(filepath); if (_.isFunction(fn)) { modelBuilder.mixins.define(name, fn); } } }); }); }; Registry.prototype.defineModels = function(dataSource, schemas) { var ds = _.isString(dataSource) ? this.dataSources[dataSource] : dataSource; return ds.modelBuilder.buildModels(schemas); }; Registry.prototype.attachModel = function(modelClass) { var definition = this.modelDefinitions[modelClass.modelName]; if (definition) { var dataSource = definition.dataSource || 'db'; var ds = this.dataSources[dataSource]; if (!ds) return console.warn('WARNING: Invalid DataSource: ', dataSource); ds.attach(modelClass); } }; Registry.prototype.applySourceFile = function(modelClass) { var definition = this.modelDefinitions[modelClass.modelName] if (definition) { if (definition.sourceFile && fs.existsSync(definition.sourceFile)) { var fn = require(definition.sourceFile); if (_.isFunction(fn)) fn.call(this, modelClass); } } }; Registry.prototype.finalizeModel = function(modelClass) { modelClass.emit('attached', this); modelClass.emit('boot', this); }; module.exports = Registry; function classify(string) { return _.startCase(string + '').replace(/\s/g, ''); }; function findConfigFiles(configDir, env, name) { var master = ifExists(name + '.json'); if (!master && (ifExistsWithAnyExt(name + '.local') || ifExistsWithAnyExt(name + '.' + env))) { console.warn('WARNING: Main config file "' + name + '.json" is missing'); } if (!master) return []; var candidates = [ master, ifExistsWithAnyExt(name + '.local'), ifExistsWithAnyExt(name + '.' + env) ]; return candidates.filter(function(c) { return c !== undefined; }); function ifExists(fileName) { var filepath = path.resolve(configDir, fileName); return fs.existsSync(filepath) ? filepath : undefined; } function ifExistsWithAnyExt(fileName) { return ifExists(fileName + '.js') || ifExists(fileName + '.json'); } }; function loadConfigFiles(files) { return files.map(function(f) { var config = require(f); Object.defineProperty(config, '_filename', { enumerable: false, value: f }); return config; }); }; function mergeConfigFiles(config, files) { var modelSources = []; var mixinSources = []; _.each(loadConfigFiles(files), function(c) { if (!_.isObject(c)) return; var dirname = path.dirname(c._filename); if (_.isObject(c._meta) && _.isArray(c._meta.sources)) { var resolvedModels = resolveSources(dirname, c._meta.sources); modelSources = modelSources.concat(resolvedModels); } if (_.isObject(c._meta) && _.isArray(c._meta.mixins)) { var resolvedMixins = resolveSources(dirname, c._meta.mixins); mixinSources = mixinSources.concat(resolvedMixins); } _.merge(config, _.omit(c, '_meta')); }); return { models: modelSources, mixins: mixinSources }; }; function resolveSources(dirname, sources) { return _.map(sources, function(src) { if (_.isString(src) && src.indexOf('.') === 0) { return path.resolve(dirname, src); } else { return src; } }); }; function tryReadDir() { try { return fs.readdirSync.apply(fs, arguments); } catch (e) { return []; } };