-
Notifications
You must be signed in to change notification settings - Fork 307
Core Calipso
The lib/calipso.js library is the engine of Calipso, it must be included in every module, and binds together all of the libraries, modules, helper classes, theming and is ultimately responsible for responding to a user request.
To import the Calipso library as connect middleware:
var calipso = require("calipso");
app.use(calipso.calipsoRouter(app, app.set('config'), next);
The express object is passed through to enable it to be linked to the Calipso object, and hence be available (e.g. to access configuration via app.set('config') within any module).
Note - this can probably be refactored to make it look much less clunky :)
The Calipso object exports the following:
Export | Description |
---|---|
lib | Common 3rd party libraries (avoids modules having to reimport these everywhere). |
dynamicHelpers | Helper functions (uninitialised) that can be used in views. |
getDynamicHelpers | Initialises helper functions for a specific request |
mr | Tracks running map reduce operations. |
sessionCache | Local cache for sessions (if required - not used currently) |
data | Used to hold simple core cached data (e.g. content types). |
e | Holder for events and event listeners. |
theme | The loaded theme object (created during bootstrap). |
modules | All loaded modules. |
cache | Theme / rendering cache library - not currently used. |
date | Date library |
form | Form library |
table | Table library |
link | Link library |
menu | Menu library |
event | Event library |
utils | General utils |
calipsoRouter | The middleware that responds to a request. |
As all modules are expected to require the calipso library, all of these functions are typically available within any module e.g.
calipso.form.render(form,formValues,req,next);
or
var myDate = calipso.date.parseDate(format,dateString,settings);
These are all core dependencies of Calipso, that are all available via calipso.lib:
fs: require('fs'),
path: require('path'),
express: require('express'),
step: require('step'),
sys: require('sys'),
mongoose: require('mongoose'),
url: require('url'),
ejs: require('ejs'),
pager: require('utils/pager'),
prettyDate: require('utils/prettyDate.js'),
crypto: require('utils/crypto.js'),
connect: require('connect'),
_:require('underscore')
For example:
calipso.lib.step();
The exported calipsoRouter function is loaded by express as middleware, that then responds to each request.
In Pseudo code, the responsibility of the calipsoRouter is as follows:
-
Initialise Calipso
- Store the Config (calipso.config)
- Create an Event Emitter (calipso.e) * Configure logging (using calipso.lib.winston)
- Load the theme
- Load the modules
- Initialise the modules
-
Create and return middleware function (req,res,next) that:
- Initialises menu objects in response
- Process form if required
- Pass request to the event based module router
The following sections will describe the key elements of this flow:
Logging is controlled by Winston, and provides the following core api:
// Shortcuts to Default
calipso.log = winston.info; // Default function
// Shortcuts to NPM levels
calipso.silly = winston.silly;
calipso.verbose = winston.verbose;
calipso.info = winston.info;
calipso.warn = winston.warn;
calipso.debug = winston.debug;
calipso.error = winston.error;
For example:
calipso.error("This is an error message that will be printed to the console ...",{hello:"world"});
Produces output:
28 Jul 09:04:12 - error: This is an error message ... hello=world
Logging is configured as part of the core configuration, accessible via the core administration page.
logs:
level: 'info',
conso : {
enabled: true
},
file: {
enabled: false,
filepath: 'logs/calipso.log'
}
}
Themes are stored in the themes folder under the root of a Calipso site, they are designed to be completely self-contained, so they can be copied and installed between sites. The full explanation of how a theme is structured is described here: Themes, this section will explain how they are loaded by the core.
Themes are loaded by asynchronously by calipso via the loadTheme function, that wraps the core theme management library defined in lib/Theme.js.
Module loading is done asynchronously. The approach is very simple and can be described in the following pseudo code:
- Scan all the module folders (anything under modules) and build an array of modules available.
- Scan the configuration to check which of these modules are enabled (if they are not in the configuration then they are not enabled).
- Initialise the calipso.modules object with the list of all modules, including those enabled.
- Load the 'about' info from the package.json.
- Load any module templates into the template cache.
- Create the dependency event listener network, creating event listeners for dependent modules (used during initialisation). In this function, if a module has a dependency that is not enabled, that module is disabled.
After the modules have been loaded, they are then initialised. This is the part where we first require the module, and then call the module initialisation functions to actually enable the module. This is all executed asynchronously, using the core 'INIT' event model defined for modules in lib/Event.js to announce that a module has initialised.
The following pseudo-code describes the process:
- Loop through all modules that are enabled, have no dependencies, and are not marked as 'last'.
- Initialise the module.
- ** On receipt of an INIT event (on listener attached in step 6 above), a module will check if all of its dependencies have been met (e.g. if it has received events for all of the things it depends on), if so, it then iniatilises.
- ** On recepit of an INIT event, check if all modules have been initialised - if so, call the init callback for the application.
In this manner, eventually all dependent modules will be initialised as they recieve notification from modules that they depend on. If dependencies are not met, the module cannot be initialised and should be safely disabled.
At this point Calipso is fully initialised, and ready to respond to a request.
In exactly the same way as the modules are initialised, they are also routed. The key difference is that the events, and the event listeners must have request scope (not server scope as in the initialisation process). This is why the events related to routing are attached to the request object itself, and so re-created with each request.
The pseudo code is very similar, but uses different functions (e.g. it makes heavy use of lib/Router.js).
- Create the dependency event listener network, creating event listeners for dependent modules (used during routing).
- Create a router event emittter and attach it to the request object (req.event rather than calipso.event).
- Loop through all modules that are enabled, are not last and have no dependencies:
- Call the module route function, passing in the current request, response objects.
- ** On receipt of an ROUTED event, a module will check if all of its dependencies have been met (e.g. if it has received events for all of the things it depends on), if so, it then call the module route function.
- ** Once all modules have been routed, return the response to the user.