modelDefs: ['_loadModules', function normalizeModelDefs (cb) { util.each(sails.models, function(modelDef, modelId) { self.normalizeModelDef(modelDef, modelId, ""); }); util.each(sails.apps, function(app, appId) { util.each(app.models, function(modelDef, modelId) { self.normalizeModelDef(modelDef, modelId, appId); }); }); cb(null, sails); }],
function loadHookDefinitions (hooks, cb) { // Mix in user-configured hook definitions util.extend(hooks, sails.config.hooks); // Make sure these changes to the hooks object get applied // to sails.config.hooks to keep logic consistent // (I think we can get away w/o this, but leaving as a stub) // sails.config.hooks = hooks; // If user configured `loadHooks`, only include those. if ( sails.config.loadHooks ) { if ( ! util.isArray(sails.config.loadHooks) ) { return cb('Invalid `loadHooks` config. '+ 'Please specify an array of string hook names.\n' + 'You specified ::' + util.inspect(sails.config.loadHooks) ); } util.each(hooks, function (def, hookName) { if ( !util.contains(sails.config.loadHooks, hookName) ){ hooks[hookName] = false; } }); sails.log.verbose('Deliberate partial load-- will only initialize hooks ::', sails.config.loadHooks); } return cb(); }
util.each(modelDefinitions, function eachModelDef (modelDef) { // Keep track of generated unique connection IDs var connectionIDs = []; util.each(modelDef.connections, function eachConnection (connection) { // Track unique, process-wide identifiers for each connection var connectionID = 'adhoc_adapter_' + i; connectionIDs.push(connectionID); i++; // Create and save new ad-hoc adapter adHocAdapters[connectionID] = _cloneAdapter({ adapterID: connection.adapter, adapterDefs: sails.adapters, connection: connection, config: modelDef.config }); }); // Populate the `adapter` property in the model definition // with an array of the uniquely generated connection ID strings for this model. // TODO: in Waterline core, use `connectionNames` instead (or something like that) sails.log.silly('Setting Model.adapter with ad-hoc clone ids => ', connectionIDs); modelDef.adapter = connectionIDs; // Old way (replaced w/ generated connection names- since uniquness guarantee was hard to achieve) // :::::::::::::::::::::::::::::::::::: // Then pluck out the adapter ids from the model's connections // and plug them as a list of strings into `Model.adapter` // modelDef.adapter = util.pluck(modelDef.connections, 'adapter'); });
util.each(sails.apps, function(app, appId){ var mDefs = app.models; util.each(mDefs, function loadModelsIntoWaterline (modelDef, modelID) { sails.log.silly('Registering model `' + modelID + '` in Waterline (ORM)'); waterline.loadCollection( Waterline.Collection.extend(modelDef) ); }); });
_createMiddleware: function (detectedViews, appId) { // If there are any matching views which don't have an action // create middleware to serve them util.each(detectedViews, function (view, id) { // Create middleware for a top-level view if (view === true) { // sails.log.verbose('Building middleware chain for view: ', id); this.middleware[id] = this._serveView(id, subViewId, appId); } // Create middleware for each subview else { if(appId === "") { this.middleware[id] = {}; for (var subViewId in detectedViews[id]) { // sails.log.verbose('Building middleware chain for view: ', id, '/', subViewId); this.middleware[id][subViewId] = this._serveView(id, subViewId, appId); } } else { this.middleware[appId] = this.middleware[appId] || {}; this.middleware[appId][id] = {}; for (var subViewId in detectedViews[id]) { // sails.log.verbose('Building middleware chain for view: ', id, '/', subViewId); this.middleware[appId][id][subViewId] = this._serveView(id, subViewId, appId); } } } }, this); },
util.each(sails.config.policies, function (_policy, controllerId) { // Accept `FooController` or `foo` // Case-insensitive controllerId = util.normalizeControllerId(controllerId); // Controller-level policy :: // Just map the policy to the controller directly if ( ! util.isDictionary(_policy) ) { mapping[controllerId] = this.normalizePolicy(_policy); return; } // Policy mapping contains a sub-object :: // So we need to dive in and build/normalize the policy mapping from here // Mapping each policy to each action for this controller mapping[controllerId] = {}; util.each( _policy, function (__policy, actionId) { // Case-insensitive actionId = actionId.toLowerCase(); mapping[controllerId][actionId] = this.normalizePolicy(__policy); }, this); }, this);
startORM: function(cb, stack) { var modelDefs = stack.modelDefs.models; // -> Build adHoc adapters (this will add `adapter` key to models) // (necessary for loading the right adapter config w/i Waterline) var i = 0; var adHocAdapters = _buildAdHocAdapterSet(modelDefs, i); util.each(sails.apps, function(app, appId){ var mDefs = app.models; _.merge(adHocAdapters, _buildAdHocAdapterSet(mDefs, _.keys(adHocAdapters).length)); }); sails.adHocAdapters = adHocAdapters; // -> Instantiate ORM in memory. // -> Iterate through each model definition: // -> Create a proper Waterline Collection for each model // -> then register it w/ the ORM. sails.log.verbose('Starting ORM...'); var waterline = new Waterline(); util.each(modelDefs, function loadModelsIntoWaterline (modelDef, modelID) { sails.log.silly('Registering model `' + modelID + '` in Waterline (ORM)'); waterline.loadCollection( Waterline.Collection.extend(modelDef) ); }); util.each(sails.apps, function(app, appId){ var mDefs = app.models; util.each(mDefs, function loadModelsIntoWaterline (modelDef, modelID) { sails.log.silly('Registering model `' + modelID + '` in Waterline (ORM)'); waterline.loadCollection( Waterline.Collection.extend(modelDef) ); }); }); // -> "Initialize" ORM // : This performs tasks like managing the schema across associations, // : hooking up models to their connections, and auto-migrations. waterline.initialize({ // Build `adHocAdapters` // The set of working adapters waterline will use internally // Adapters should be built using the proper adapter definition with config // from the source connection mixed-in adapters: adHocAdapters }, cb); },
initializeHooks(hooks, function (err) { if (err) return cb(err); // Mix hooks into sails.hooks util.each(hooks, function (hook, hookID) { sails.hooks[hookID] = hook; }); sails.log.verbose('Initialized ' + Object.keys(hooks).length + ' user hook(s)...'); return cb(); });
/** * Recursively bind an array of targets in order * * TODO: Use a counter to prevent indefinite loops-- * only possible if a bad route is bound, * but would still potentially be helpful. * * @api private */ function bindArray(path, target, verb, options) { var self = this; var sails = this.sails; if (target.length === 0) { sails.log.verbose('Ignoring empty array in `router.bind(' + path + ')`...'); } else { // Bind each middleware fn util.each(target, function(fn) { bind.apply(self,[path, fn, verb, options]); }); } }
configure: function () { var self = this; ////////////////////////////////////////////////////////////////////////////////////////// // Backwards compat. for `config.adapters` ////////////////////////////////////////////////////////////////////////////////////////// // `sails.config.adapters` is now `config.connections` if (sails.config.adapters) { // `config.adapters.default` is being replaced with `config.model.connections` if (sails.config.adapters['default']) { sails.log.verbose('Deprecation warning :: Replacing `config.adapters.default` with `config.model.connections`....'); sails.config.model.connections = sails.config.adapters['default']; } // Merge `config.adapters` into `config.connections` sails.log.verbose('Deprecation warning :: Replacing `config.adapters` with `config.connections`....'); util.each(sails.config.adapters, function (legacyAdapterConfig, connectionName) { // Ignore `default` if (connectionName === 'default') { return; } // Normalize `module` to `adapter` var connection = util.clone(legacyAdapterConfig); connection.adapter = connection.module; delete connection.module; sails.log.verbose( 'Deprecation warning :: ' + 'Replacing `config.adapters['+connectionName+'].module` ' + 'with `config.connections['+connectionName+'].adapter`....'); sails.config.connections[connectionName] = connection; }); delete sails.config.adapters; } // Listen for reload events, which will just run the initialize over again sails.on('hook:orm:reload', function() { self.initialize(function(err) { // If the re-initialization was a success, trigger an event // in case something needs to respond to the ORM reload (e.g. pubsub) if (!err) { sails.emit('hook:orm:reloaded'); } }); }); },
function loadHookDefinitions (hooks, cb) { // Mix in user-configured hook definitions util.extend(hooks, sails.config.hooks); // If user configured `loadHooks`, only include those. if ( sails.config.loadHooks ) { if ( ! util.isArray(sails.config.loadHooks) ) { return cb('Invalid `loadHooks` config. '+ 'Please specify an array of string hook names.\n' + 'You specified ::' + util.inspect(sails.config.loadHooks) ); } util.each(hooks, function (def, hookName) { if ( !util.contains(sails.config.loadHooks, hookName) ){ hooks[hookName] = false; } }); sails.log.verbose('Deliberate partial load-- will only initialize hooks ::', sails.config.loadHooks); } return cb(); }
bindPolicies: function() { // sails.log.verbose('Binding policies :: \n', this.mapping, // '\nto controllers :: \n', sails.middleware.controllers); // Policies can be bound to: // -> controllers _bindPolicies(this.mapping, sails.middleware.controllers); var self = this; util.each(sails.apps, function(app, key){ _bindPolicies(self.mapping, app.middleware.controllers); }); // NOTE: // In the past, policies for views were bound here. // // Emit event to let other hooks know we're ready to go sails.log.verbose('Policy-controller bindings complete!'); sails.emit('hook:policies:bound'); this.ready = true; },
util.each(sails.apps, function(app, appId) { util.each(app.models, function(modelDef, modelId) { self.normalizeModelDef(modelDef, modelId, appId); }); });
sails.on('router:after', function () { util.each(self.routes.after, function (middleware, route) { sails.router.bind(route, middleware); }); });
bindView: function ( path, target, verb, options ) { // Get view names var view = target.view.split('/')[0]; var subview = target.view.split('/')[1] || 'index'; // Look up appropriate view and make sure it exists var viewMiddleware = sails.middleware.views[view]; // Dereference subview if the top-level view middleware is actually an object if (util.isPlainObject(viewMiddleware)) { viewMiddleware = viewMiddleware[subview]; } // If a view was specified but it doesn't match, // ignore the attempt and inform the user if ( !viewMiddleware ) { sails.log.error( 'Ignoring attempt to bind route (' + path + ') to unknown view: ' + target.view ); return; } // Make sure the view function (+/- policies, etc.) is usable // If it's an array, bind each action to the destination route in order else if (util.isArray(viewMiddleware)) { util.each(viewMiddleware, function (fn) { sails.router.bind(path, viewHandler(fn), verb, target); }); return; } // Bind an action which renders this view to the destination route else { sails.router.bind(path, viewHandler(viewMiddleware), verb, target); return; } // Wrap up the view middleware to supply access to // the original target when requests comes in function viewHandler (originalFn) { if ( !util.isFunction(originalFn) ) { sails.log.error( 'Error binding view to route :: View middleware is not a function!', originalFn, 'for path: ', path, verb ? ('and verb: ' + verb) : ''); return; } // Bind intercepted middleware function to route return function wrapperFn (req, res, next) { // Set target metadata req.target = { view: target.view }; // Merge in any view locals specified in route options if (options.locals) { util.extend(res.locals || {}, options.locals); sails.log.silly('Merged in view locals..',res.locals); } // Call actual controller originalFn(req, res, next); }; } },
sails.on('router:before', function () { util.each(self.routes.before, function (middleware, route) { sails.router.bind(route, middleware); }); });
sails.on('router:after', function () { util.each(self.routes.after, function (middleware, route) { middleware._middlewareType = self.identity.toUpperCase() + ' HOOK' + (middleware.name ? (': ' + middleware.name): ''); sails.router.bind(route, middleware); }); });
util.each(middlewareSet, function (_c, id) { var topLevelPolicyId = mapping[id]; var actions, actionFn; var controller = middlewareSet[id]; // If a policy doesn't exist for this controller, use '*' if ( util.isUndefined(topLevelPolicyId) ) { topLevelPolicyId = mapping['*']; } // sails.log.verbose('Applying policy :: ', topLevelPolicyId, ' to ', id); // Build list of actions if ( util.isDictionary(controller) ) { actions = util.functions(controller); } // If this is a controller policy, apply it immediately if ( !util.isDictionary(topLevelPolicyId) ) { // :: Controller is a container object // -> apply the policy to all the actions if ( util.isDictionary(controller) ) { // sails.log.verbose('Applying policy (' + topLevelPolicyId + ') to controller\'s (' + id + ') actions...'); util.each(actions, function(actionId) { actionFn = controller[actionId]; controller[actionId] = topLevelPolicyId.concat([actionFn]); // sails.log.verbose('Applying policy to ' + id + '.' + actionId + '...', controller[actionId]); }, this); return; } // :: Controller is a function // -> apply the policy directly // sails.log.verbose('Applying policy (' + topLevelPolicyId + ') to top-level controller middleware fn (' + id + ')...'); middlewareSet[id] = topLevelPolicyId.concat(controller); } // If this is NOT a top-level policy, and merely a container of other policies, // iterate through each of this controller's actions and apply policies in a way that makes sense else { util.each(actions, function(actionId) { var actionPolicy = mapping[id][actionId]; // sails.log.verbose('Mapping policies to actions.... ', actions); // If a policy doesn't exist for this controller/action, use the controller-local '*' if ( util.isUndefined(actionPolicy) ) { actionPolicy = mapping[id]['*']; } // if THAT doesn't exist, use the global '*' policy if ( util.isUndefined(actionPolicy) ) { actionPolicy = mapping['*']; } // sails.log.verbose('Applying policy (' + actionPolicy + ') to action (' + id + '.' + actionId + ')...'); actionFn = controller[actionId]; controller[actionId] = actionPolicy.concat([actionFn]); }, this); } });
return function loadSocketIO (cb) { sails.log.verbose('Configuring socket (ws://) server...'); var socketConfig = sails.config.sockets; // Socket.io server (WebSockets+polyfill to support Flash sockets, AJAX long polling, etc.) var io = sails.io = sails.ws = SocketServer.listen(sails.hooks.http.server, { logger: { info: function (){} } }); // If logger option not set, use the default Sails logger config if (!socketConfig.logger) { var logLevels = { 'silent': 0, 'error': 1, 'warn': 2, 'debug': 4, // Socket.io flips these around (and it considers debug more verbose than `info`) 'info': 3, // Socket.io flips these around 'verbose': 4 // Socket.io has no concept of `verbose` }; io.set('log level', logLevels[sails.config.log.level] || logLevels['info']); io.set('logger', { error: sails.log.error, warn: sails.log.warn, info: sails.log.verbose, debug: sails.log.verbose // socket.io considers `debug` the most verbose config, so we'll use verbose to represent it }); } // Process the Config File util.each(socketConfig, function(value, propertyName) { // Handle `Memory` adapter value if (propertyName === 'adapter' && value === 'memory') return; // Setup custom socket.io MQ (pubsub) store(s) if (propertyName === 'adapter' && value === 'redis') { var host = socketConfig.host || '127.0.0.1'; var port = socketConfig.port || 6379; var pub = createRedisConnection(port, host); var sub = createRedisConnection(port, host); var client = createRedisConnection(port, host); var storeConfig = { redisPub: pub, redisSub: sub, redisClient: client }; // Add a pointer to the redis, required with Auth if(socketConfig.pass) { storeConfig.redis = Redis; } io.set('store', new RedisStore(storeConfig)); return; } // Configure logic to be run before allowing sockets to connect if (propertyName === 'authorization') { // Custom logic if (util.isFunction(value)) { io.set('authorization', value); return; } // `authorization: true` means go ahead and use the default behavior if (value === true) { io.set('authorization', Socket.authorization); return; } // Otherwise skip the authorization step io.set('authorization', false); return; } // If value is explicitly undefined, do nothing if (util.isUndefined(value)) return; // In the general case, pass the configuration straight down to socket.io io.set(propertyName, value); }); // For later: // io.configure('development', function() {}); // io.configure('production', function() {}); // Link Socket.io requests to a controller/action // When a socket.io client connects, listen for the actions in the routing table // Authorization has already passed at this point! io.sockets.on('connection', Socket.connection); cb && cb(); };