Core.io

Reference

Application

Application Context

During the initialization phase of modules core.io will call the module's exported init method with two arguments. The first argument is an instance of your application, this instance is called the application context and the convention through the source code, examples, and documentation is to name the argument context.

core.io intends to keep the global namespace unpolluted so modules should not have strong dependencies on core.io beyond the init function.

This context acts a little bit like an [IOC][ioc] container in that it is intended to make your code modular and provide a point to extend your application at runtime at the same time that it provides access to features added by other modules.

You will use this context to resolve dependencies at runtime, and to provide new capabilities to your application.

An example of this would be the following hypothetical module:

module.exports.init = function(context, config) {
    const crud = new Crud(config);

    return context.resolve('persistence', 'server').then(() => {
        context.provide('crud', crud.initialize(context.server));
    }).catch(context.handleModuleError.bind(true));
};

This module declares two dependencies; persistence, and server. Modules are resolved asynchronously so the then code will be executed after both persistence, and server are available.

context.provide will expose a crud property and make it available to other parts of your code.

NOTE: Instead of the "crud" string we recommend you use moduleId which is part of the config object. You will learn later how to configure a module, but for now, know that if the configuration you provide does not include a moduleId property, then core.io will use the default name of the module.

Application Lifecycle

Configuration

While the intention of core.io is to adhere to the idea of convention over configuration, it still grants you, the developer, full control over most aspects of your application by letting you override default values.

core.io configuration process is purportedly simple, a core.io application takes an options object with configuration parameters and overrides. core.io does not really care how you come up with that object.

However, the Application class provides a helper static method to collect, merge, and resolve dependencies of configuration files that are located in the ./config folder of a project.

The resulting configuration object will be made available at runtime on the application context as context.config.

When you create a new Application instance you can pass an options object to it's constructor.

This options object has two purposes. If you define a configuration key, it's value will be added to context.config.

All other keys in this object will extend the application instance, like a [mixin][mixin]. The application instance extends itself with this object in it's init method which is called directly from the constructor.

You can use it to override methods before the instance makes use of any of them or to add new methods to your instance.

var App = require('core.io').Application;

/*
 * Autload and merge files inside
 * `config/`
 */
var config = App.loadConfig({
    //...default values
}, true);

var app = new App({
    myCustomMethod: function(e) {
        this.emit('custom.event', e);
    },
    config: config
});

app.myCustomMethod({});

Configuration instance

For convenience core.io wraps the config object with a get and set methods.

This is so that you can access a deep object without fear of some object in the path not being defined. It also enables you to provide a default value for such cases.

//Get the value of "environment" defined in config/app.js
//return "production" if undefined.
let environment = context.config.get('app.environment', 'production');

It's more useful when you need to access a deeply nested object:

//Get the value of "repl.options.prompt", return "poke-repl >" if undefined.
let prompt = context.config.get('repl.options.prompt', 'poke-repl >');

Module Configuration

When core.io registers a module, first it will require the module and then will look for a key in context.config that matches the module's moduleId. It will then call module.init with a reference to the value of this key.

Pseudo code to illustrate:

let moduleId = 'persistence';
let config = this.config.get(moduleId, {});
module.init(this, config);

Configuration Files

Application.loadConfig will load all configuration files found inside the ./config directory of your application.

It will then load the files, and merge them in a single object using the file name as a key.

If you have a configuration file that has the same name as a given module's moduleId then the contents of that file will be passed to the module during the initialization phase.

In a configuration file you can reference values from the same configuration object or from other configuration objects. Using two different syntaxes you can reference strings or objects:

  • Strings: ${app.name}
  • Objects: @{app.locals}

The configuration solve routine will solve all cross references between configuration files. It runs after merging all files into a single object.

As an example, ${app.name} will be resolved to config.app.name:

  • config/app.js:
module.exports = {
  name: 'MyApplication'
};
  • config/repl.js:
module.exports = {
  prompt: '${app.name}'
};

There is also the possibility of processing the contents of a configuration file after it has been merged and loaded.

If you export a function named afterSolver it will be called after all dependencies have been resolved. The function will be called with the whole configuration object.

module.exports.afterSolver = function(config) {
    config.set('amqp.amqp', require('amqp'));
};

Configuration files are regular JavaScript files, which means you can build different logic into them.

Under the hood core.io uses the [simple config loader][scl] package. You can read more in the packages repository.

core.io provides a convenience method to collect these configuration files.

var App = require('core.io').Application;

/*
 * Autoload and merge files inside
 * `config/`
 */
var config = App.loadConfig({
    //...default values
}, true);

var app = new App({
    //Top level attributes will extend the application
    //instance.
    myCustomMethod: function(){},
    config: config
});

You can specify the path from where to look for the configuration files.

Modules

Core Modules

Extended Modules

Commands

Autoloading

Autoloading refers to a core.io feature which take files placed in specific directories within your project then load and wire the files into your project, or application context to be more precise.

As an example, all files under ./config can be autoloaded if you use the Application.loadConfig static method.

All files under the ./commands directory will be required and registered as commands.

If you are using the [persistence][core-persistence] module, then all files under the ./models directory will be registered as models.

But mainly, all valid modules found in the ./modules directory will be loaded and registered with the application context, meaning that to add a new module to your application you simply need to place it in the ./modules directory and then core.io will do the rest.

Note that the dependency solving cycle happens statically at runtime during the boot process of your application, so to detect a new module you need to stop and restart your application.

Module Loader

As explained earlier, all valid modules found in the modules directory will be required and then registered with the application context.

A valid module is either a javascript file exporting an init function or a directory with an index.js file exporting an init function.

Commands Loader

Configuration Loader

Model Loader