schahriar/supertask

View on GitHub
interface.js

Summary

Maintainability
B
4 hrs
Test Coverage
"use strict";

/// Required Core Modules

///
/// External Modules
const Deque = require('double-ended-queue');
///
/// Internal Modules
const SuperTaskInternal = require('./lib/SuperTask');
const ContextPermissions = require('./lib/ContextPermissions');
const TaskObject = require('./lib/TaskObject');
///
/// Predefined Types
// TYPES
const ST_LOCAL_TYPE = 0, ST_REMOTE_TYPE = 1, ST_FOREIGN_TYPE = 2;
// PERMISSIONS
const ST_NONE = 0, ST_RESTRICTED = 1, ST_MINIMAL = 2, ST_UNRESTRICTED = 3;
///
// No Operation function
function noop() { return null; }

class SuperTask extends SuperTaskInternal {
    /**
     * Creates new instance.
     * @class SuperTask
     * 
     * @example
     * var SuperTask = require('supertask');
     * var TaskManager = new SuperTask();
     * 
     * @returns {Instance} Returns a new instance of the module.
     */
    constructor(size) {
        super(size);
    }
    /**
    * Creates a new local Task. A local task is a task that is not
    * shared by any outside entity and usually performs slightly
    * faster than shared or foreign tasks as there is no compilation
    * or try/catch involved. 
    * 
    * @param {String} name - A unique name for this Task.
    * @param {Function} taskFunction - The JS function of the Task.
    * @returns {@link Task}
    */
    addLocal(name, func) {
        return this._addTask(name, func, null, ST_LOCAL_TYPE);
    }
    
    /**
     * Creates a new foreign Task. A local task is a task that is not
     * shared by any outside entity and usually performs slightly
     * faster than shared or foreign tasks as there is no compilation
     * or try/catch involved. 
     * 
     * @param {String} name - A unique name for this Task.
     * @param {Function} taskFunction - The JS function or source with
     * module.exports to be used as the function. Note that sources are compiled
     * to a function before use with about a 30ms overhead on the first call unless
     * precompile method of task is called beforehand.
     * @returns {@link Task}
     */
    addForeign(name, source) {
        // VM requires a String source to compile
        // If given source is a function convert it to source (context is lost)
        if (typeof source === 'function') {
            source = 'module.exports = ' + source.toString();
        }
        return this._addTask(name, source, null, ST_FOREIGN_TYPE);
    }
    
    /**
     * Creates a new remote Task. Remote tasks do not exist in
     * the local machine and instead a handler function is called
     * to run the function with the arguments wherever the task
     * is located. Note that the handler function takes arguments
     * of [task, name, context, args, callback].
     * 
     * @param {String} name - A unique name for this Task.
     * @param {Function|String} taskFunction - The JS function or source with
     * module.exports to be used as the function.
     * @param {Function} handler - A handler function that is called
     * when the task is executed.
     * @returns {@link Task}
     */
    addRemote(name, source, handler) {
        // VM requires a String source to compile
        // If given source is a function convert it to source (context is lost)
        if (typeof source === 'function') {
            source = 'module.exports = ' + source.toString();
        }
        return this._addTask(name, source, handler, ST_REMOTE_TYPE);
    }
    
    /**
     * Run a task with the given arguments
     * 
     * @param {String} taskName - Unique name of the task
     * @param {...*} arguments - Arguments that are passed to the Task.
     * You can call this function with any number of arguments so long
     * as the last argument is the callback.
     * @param {Function} callback - The callback that handles the response.
     * Note that the callback parameters are based on what the function calls
     * the callback with but will include `error` as the first parameter as
     * per usual NodeJS async calls.
     */
    do() {
        let args = new Array(arguments.length);
        for(let i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }
        let name = args.shift();
        let callback = noop;
        
        // Check for callback
        if (typeof args[args.length - 1] === 'function') {
            callback = args.pop();
        }
        
        this.apply(name, null, args, callback);
    }
    
    /**
     * Run a task with the given arguments and context (this)
     * 
     * @param {String} taskName - Unique name of the task
     * @param {Object} context - Passes as (this.property). Note that
     * `call`, `apply`, `self`, `recurse` are reserved properties.
     * @param {Array} arguments - An array of arguments that are passed
     * to the Task.
     * @param {Function} callback - The callback that handles the response.
     * Note that the callback parameters are based on what the function calls
     * the callback with but will include `error` as the first parameter as
     * per usual NodeJS async calls.
     */
    apply(name, context, args, callback) {
        
        // Check for mapped task
        if (!this.map.has(name)) {
            callback(new Error('Task not found!'));
            return;
        }

        let task = this.map.get(name);
        
        // Check for mapped task
        if (!task) {
            return callback(new Error('Task not found!'));
        }
        
        // Call remote tasks from the handler
        if (task.remote) {
            if (typeof task.handler !== 'function') return callback(new Error('Remote task was detected but no handler found'));
            
            return task.handler(task, name, context, args, callback);
        }
        
        // Set Globals to task's default
        let globals = task.globals;
        // Combine Permissions Context (SLOWS DOWN EXECUTION)
        if ((task.access) && (task.access !== ST_NONE)) {
            globals = ContextPermissions(globals || {}, task.access);
        }
        
        // Make sure we have a context
        if (!context) context = {};
        
        // Override context with enforced methods
        for (let key in task.context) {
            context[key] = task.context[key];
        }
        
        // Sanitize args
        args = (Array.isArray(args)) ? args : [args];

        // Compile task if it is in source form
        if (typeof task.func !== 'function') {
            try {
                let result = this._compile(task, globals);
                if (typeof result === 'function') task.func = result;
                else return callback(new Error("Unknown error occurred. Failed to compile and execution was halted."));
            } catch (e) {
                return callback(e);
            }
        }
        // Push to Queue
        this._push({
            name: name,
            globals: globals,
            context: context,
            args: args,
            task: task,
            callback: callback
        });
    }
    
    /**
     * Remove a task.
     * 
     * @param {String} name - Name of the task.
     * @returns {Boolean} - Returns true if task
     * existed and was removed or false if task did not exist
     */
    remove(name) {
        return this.map.delete(name);
    }

    /**
     * Check if task exists.
     * 
     * @param {String} name - Name of the task.
     * @returns {Boolean} exists
     */
    has(name) {
        return !!this.map.has(name);
    }

    /**
     * Get a wrapped version of the task.
     * 
     * @param {String} name - Name of the task.
     * @returns {Object} task  
     */
    get(name) {
        if (!this.has(name)) return null;
        return TaskObject.wrap(this, this.map.get(name));
    }
}

/// EXTEND PREDEFINED VARS
SuperTask.ST_LOCAL_TYPE = ST_LOCAL_TYPE;
SuperTask.ST_REMOTE_TYPE = ST_REMOTE_TYPE;
SuperTask.ST_FOREIGN_TYPE = ST_FOREIGN_TYPE;

/** No permissions. Code runs JS only. */
SuperTask.ST_NONE = ST_NONE;
/** Some permissions. Allows streams, Buffer, setTimeout, setInterval only */
SuperTask.ST_RESTRICTED = ST_RESTRICTED;
/** Minimal permissions. Allows all restricted permissions and __dirname, __filename, console globals.
 * Includes a limited require('*') function with access to 'http', 'https', 'util', 'os', 'path', 'events', 'stream', 'string_decoder', 'url', 'zlib'
 */
SuperTask.ST_MINIMAL = ST_MINIMAL;
/** UNSAFE, All permissions. Copies global scope. */
SuperTask.ST_UNRESTRICTED = ST_UNRESTRICTED;

module.exports = SuperTask;