tsOut/middleware.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Debug = require("debug");
const fs = require("fs");
const _1 = require(".");
const validators_1 = require("./validators");
const debug = Debug('nohm:middleware');
const MAX_DEPTH = 5;
function customToString(obj, depth = 0) {
    if (depth > MAX_DEPTH) {
        console.warn(new Error('nohm: middleware customToString() maxdepth exceeded').stack);
        return '';
    }
    switch (typeof obj) {
        case 'string':
            return '"' + obj + '"';
        case 'number':
            return obj.toString();
        case 'boolean':
            return obj ? 'true' : 'false';
        case 'function':
            if (obj instanceof RegExp) {
                return obj.toString();
            }
            break;
        case 'object':
            if (Array.isArray(obj)) {
                const arr = [];
                obj.forEach((val) => {
                    arr.push(customToString(val, depth + 1));
                });
                return '[' + arr.join(',') + ']';
            }
            else if (obj instanceof RegExp) {
                return obj.toString();
            }
            else {
                const arr = [];
                Object.keys(obj).forEach((val) => {
                    arr.push('"' + val + '":' + customToString(obj[val], depth + 1));
                });
                return '{' + arr.join(',') + '}';
            }
        default:
            return '';
    }
    return '';
}
function validationsFlatten(model, exclusions = {}) {
    const instance = new model();
    const definitions = instance.getDefinitions();
    let str = instance.modelName + ': {';
    /*
     * example exclusions object
     *  {
     *    // this will ignore the first validation in the validation definition array for name in the model definition
     *    name: [0],
     *    // this will completely ignore all validations for the salt property
     *    salt: true
     *  },
     */
    const exclusionsStrings = [];
    const exclusionsObject = {};
    Object.keys(exclusions).forEach((key) => {
        const value = exclusions[key];
        if (Array.isArray(value)) {
            exclusionsObject[key] = value.map((x) => !!x);
        }
        exclusionsStrings.push(key);
    });
    Object.keys(definitions).forEach((key) => {
        const isExcepted = exclusionsStrings.indexOf(key) !== -1 &&
            !exclusionsObject.hasOwnProperty(key);
        if (!isExcepted) {
            const vals = definitions[key].validations;
            if (Array.isArray(vals) && vals.length > 0) {
                str += `${key}: [`;
                const strVals = [];
                vals.forEach((val, index) => {
                    if (!exclusionsObject[key] || exclusionsObject[key][index]) {
                        strVals.push(customToString(val));
                    }
                });
                str += strVals.join(',') + '],           ';
            }
        }
    });
    return str + '}';
}
let extraFilesIndex = 0;
function wrapFile(fileStr, namespace) {
    let str = `${namespace}.extraValidations[${extraFilesIndex}]={};(function (exports) {`;
    str += fileStr;
    str += `})(${namespace}.extraValidations[${extraFilesIndex}]);`;
    extraFilesIndex++;
    return str;
}
function wrapExtraFiles(files, namespace) {
    let str = '';
    files.forEach((path) => {
        const fileStr = fs.readFileSync(path, 'utf-8');
        str += wrapFile(fileStr, namespace);
    });
    return str;
}
/**
 * Returns a middleware that can deliver the validations as a javascript file
 * and the modelspecific validations as a JSON object to the browser.
 * This is useful if you want to save some bandwith by doing the validations
 * in the browser before saving to the server.
 *
 * Example:
 *
 * ```
 *    server.use(nohm.middleware(
 *      // options object
 *      {
 *        url: '/nohm.js',
 *        namespace: 'nohm',
 *        exclusions: {
 *
 *          User: { // modelName
 *
 *            // this will ignore the second validation in the validation definition array for
 *            // the property 'name' in the model definition
 *            name: [false, true],
 *
 *            // this will completely ignore all validations for the salt property
 *            salt: true
 *          },
 *
 *          Privileges: true // this will completely ignore the Priviledges model
 *        }
 *      }
 *    ));
 * ```
 *
 * @see https://maritz.github.io/nohm/#browser-validation
 * @param {Object} options Options for the middleware
 * @param {string} [options.url='/nomValidations.js'] Url under which the js file will be available.
 * @param {object.<string, object | boolean>} [options.exclusions={}] Object containing exclusions for the
 * validations export - see example for details
 * @param {string} [options.namespace='nomValidations'] Namespace to be used by the js file in the browser.
 * @param {string} [options.extraFiles=[]] Extra files containing validations.
 * You should only use this if they are not already set via Nohm.setExtraValidations
 * as this automatically includes those.
 * @param {number} [options.maxAge=3600] Cache control in seconds. (Default is one hour)
 * @param {boolean} [options.uglify=false] True to enable minification.
 * Requires uglify-js to be installed in your project!
 * @return {Middleware~callback}
 * @instance
 * @memberof NohmClass
 */
function middleware(options, nohm = _1.nohm) {
    options = options || {};
    const url = options.url || '/nohmValidations.js';
    const namespace = options.namespace || 'nohmValidations';
    const maxAge = options.maxAge || 3600; // 1 hour
    const exclusions = options.exclusions || {};
    let extraFiles = options.extraFiles || [];
    let uglify = options.uglify || false;
    if (!Array.isArray(extraFiles)) {
        extraFiles = [extraFiles];
    }
    // collect models
    const collectedModels = [];
    const models = nohm.getModels();
    Object.keys(models).forEach((name) => {
        const model = models[name];
        let exclusion = exclusions[name];
        if (exclusion === true) {
            return; // exception set, but no fields
        }
        else {
            if (exclusion === true || exclusion === false) {
                exclusion = {};
            }
            collectedModels.push(validationsFlatten(model, exclusion));
        }
    });
    let str = `var nohmValidationsNamespaceName = "${namespace}";
  var ${namespace}={"extraValidations": [], "models":{${collectedModels.join(',')}}};
  // extrafiles
  ${wrapExtraFiles(extraFiles, namespace)}
  // extravalidations
  ${wrapExtraFiles(nohm.getExtraValidatorFileNames(), namespace) /* needs to somehow access the same thing */}
  // validators.js
  ${fs.readFileSync(validators_1.universalValidatorPath, 'utf-8')}`;
    if (uglify) {
        try {
            // tslint:disable-next-line:no-implicit-dependencies
            uglify = require('uglify-js');
        }
        catch (e) {
            console.warn('You tried to use the uglify option in Nohm.connect but uglify-js is not requirable.', 'Continuing without uglify.', e);
        }
        if (uglify.parser && uglify.uglify) {
            const jsp = uglify.parser;
            const pro = uglify.uglify;
            const ast = jsp.parse(str);
            // ast = pro.ast_mangle(ast); // TODO: test if this works with our globals
            const squeezed = pro.ast_squeeze(ast);
            str = pro.gen_code(squeezed);
        }
    }
    debug(`Setting up middleware to be served on '%s' with namespace '%s' and collected these models: %o`, url, namespace, collectedModels);
    /**
     * This function is what is returned by {@link NohmClass#middleware}.
     *
     * @callback Middleware~callback
     * @name MiddlewareCallback
     * @function
     * @param {Object} req http IncomingMessage
     * @param {Object} res http ServerResponse
     * @param {function} [next] Optional next function for express/koa
     * @memberof Nohm
     */
    return (req, res, next) => {
        if (req.url === url) {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/javascript');
            res.setHeader('Content-Length', str.length.toString());
            res.setHeader('Cache-Control', 'public, max-age=' + maxAge);
            res.end(str);
        }
        else if (next && typeof next === 'function') {
            next();
        }
    };
}
exports.middleware = middleware;
//# sourceMappingURL=middleware.js.map