/stumble-core

A loading platform for extensible extensions.

Primary LanguageJavaScriptMIT LicenseMIT

stumble-core

Extensible extensions.

NPM version Downloads

stumble-core is an extension loader, and provides the base for the stumble module.

It's slightly tailored towards Stumble's use cases, but generic enough that other projects might find use in the structure that it provides.

This module is written in a mix of Node.js style ES5, and ES6. It is intended for use in modern Node 5.0.0 and newer environments.

While the module does aim for good performance, its intended use case is to be a front heavy loader for a long-running application, so it takes some liberties in making sure the structure it provides is sound, with regards to certain levels of type checking and object translation, and that its API is simple.

Usage

Install with npm.

$ npm install stumble-core

require('stumble-core') returns the StumbleCore base class, which can be extended upon, or instantiated.

Base Class

class StumbleCore extends EventEmitter (options :: Object)

The base class constructor takes a single argument, an options object. This options object cares about one property, cacheduration, which should be a number representing the time in milliseconds that the internal cache hangs on to superloaded extensions. This is a fairly internal affair, and generally the options object can safely be omitted, and ignored.

const StumbleCore = require('stumble-core');

const core = new StumbleCore();

class MyApp extends StumbleCore { ... }

Properties

aliases :: Map - Holds handle aliases, mapped against their true handle.

commands :: Map - Holds loaded commands, referenced by their handle.

extensions :: Map - Holds loaded extensions, referenced by their handle.

Methods

use

use (
    extension :: Object|Array<Object>
    [, pending :: Array<Object>
    [, index :: Number]]
   )

The use method loads extension-like objects, translating them into Extension objects, and placing these new objects in the .extensions Map instance property.

The extension argument may be a single extension-like object, or an array of extension-like objects. The pending and index arguments should be omitted from any outside call to use. These arguments are used internally, when the use method employs a recursive strategy to attempt loading missing extension .needs.

use returns the instance of StumbleCore for chaining.

See: Extensions

A completely contrived example of loading an extension-like object:

core.use({
  handle: 'speaker',
  needs: ['input'],
  hooks: ['transformer'],
  exec: function speaker (data, roll) {
    const io = this.execute('input::getio');

    io.pipe(data.transformed || data.original);

    if (roll[0]) this.execute('log', { timestamp: roll[0].timestamp });
  }
})

execute

execute (handle :: String, data :: Object, roll :: Array)

The execute method executes the .exec property of the extension identified by its .handle, passed as the handle argument.

The .exec function is passed the same data argument passed to execute as its first argument. Stylistically, this should be an object containing 'parameters' as properties. Realistically, it can be any type.

The .exec function is passed a roll as its second argument. The roll is either null, in the event that the extension has no .hooks, or an array of the results of executing each hook.

The contextual this of the .exec function is set as the calling instance of StumbleCore.

execute returns the result of the .exec function, hold for the following exception:

  • If an extension identified by handle is not present, or is present but has no .exec function, execute simply returns null. This is to allow for NOP like behaviour from optional .hooks.

A short example:

core.use({
  handle: 'logger',
  exec: function logger (data) {
    Object.keys(data).forEach(key => console.log(data[key]));
  }
});

...

core.execute('logger', { prop: 'Hello, world!' });

define

define (command :: Object|Array<Object>)

The define method loads command-like objects, translating them into Command objects, and placing these new objects in the .commands Map instance property.

The command argument may be a single command-like object, or an array of command-like objects.

define returns the instance of StumbleCore for chaining.

See: Commands

A simple example:

core.define({
  handle: 'log-things',
  exec: function (data) {
    this.execute('logger', data);
  }
});

invoke

invoke (handle :: String, data :: Object)

The invoke method invokes the .exec property of the command identified by its .handle, passed as the handle argument.

The .exec function is passed the same data argument passed to invoke as its first, and only, argument. Stylistically, this should be an object containing 'parameters' as properties. Realistically, it can be any type.

The contextual this of the .exec function is set as the calling instance of StumbleCore.

Attempting to invoke a command not present will throw an Error.

invoke returns the result of the .exec function.

A simple example:

core.invoke('log-things', { one: 'hello', two: 'world' });

dequire

static dequire (userpath :: String)

A static helper method for loading user space modules. This method is a wrapper around require, which invalidates the resulting require.cache store for the loaded module, and returns the exports. Useful for hot swapping extensions, and continuous development.

It should not be used to load systems modules (path, url, http, etc.).

Extensions

Extensions are small packages of code. Extensions can be executed, optionally hooking other extensions beforehand. Extensions can depend on other extensions. Extensions can contain other extensions, and commands, acting as namespaces.

Extensions are created from shallowly copying the properties of an existing extension-like object. They are loaded with the use method, and executed with the execute method.

An object is considered extension-like when it has any, or all, of the following properties:

.handle :: String

While not strictly required, the .handle property must be present if an extension is to be stored. Errors will be thrown if .exec or .extensions is present without a .handle, or if a duplicate .handle is used.

.version :: String

An arbitrary version string. Not used by StumbleCore in any way.

.needs :: Array<String>

An array of extension handles that the extension depends on, and that must be loaded beforehand. If the use method is passed an array of extensions, and a need is not met in one of them, a simple strategy of scanning the rest of the array, and recursively superloading the required extension is employed.

The .needs property is discarded after load.

.hooks :: Array<String>

An array of extension handles to execute before executing the .exec function. Each hook is executed with the same data argument passed to the original execute method call, allowing them to mutate the object. The result of each hook is mapped back to an array, which passed as the roll argument to the .exec function of the extension doing the hooking (null if there are no hooks).

.exec :: Function

The .exec property is the function executed by the execute instance method.

function executable (data, roll) { ... }

.init :: Function

Extension-to-be objects may also contain an .init property. This function is invoked during the loading of the extension (but before the extension is actually set in stone). The function is passed the StumbleCore instance as its first argument, and also sets the instance as the contextual this of the function.

.term :: Function

An optional property, and the counter to .init, intended for use as a terminating function. Since StumbleCore does not actually unload extensions, this is not used internally in any way. However, an extension unloader could be implemented as an extension, or subclass feature set, and may make use of this function.

The .init function is discarded from the final extension object.

.extensions :: Array<Object>

The .extensions property can be used to house other extensions. These extensions are loaded before the parent extension.

Each extension in the .extensions array is mapped to its .handle, and the resulting array is stored back on the final extension object, showing a parental relationship.

.commands :: Array<Object>

The .commands property can be used to house command objects. These commands are defined before the parent extension is loaded.

Each command in the .commands array is mapped to its .handle, and the resulting array is stored back on the final extension object, showing a parental relationship.

Commands

Commands are miniature extensions, intended to be exposed to an end-user through some means. They are defined with the define method, and invoked with the invoke method.

Commands are created from shallowly copying the properties of an existing object.

The only properties that are copied over are:

.handle :: String

The identifier of the command. Used by the invoke method. It is required.

Command handles can not contain whitespace, and may not be duplicated. Everything else is fair game.

.exec :: Function

The executable block used by the invoke method. It is required.

.aliases :: Array<String>

An array of handles to use as aliases for the command. Same rules as .handle apply to each.

.info :: Function

A completely optional function, which can be used to implement information gathering for commands.

License

MIT.


Enjoy!

Colin 'Oka' Hall-Coates

oka.io | @Okahyphen