UNPKG

koa-neo4j

Version:

Rapidly create REST APIs, powered by Koa and Neo4j -- batteries included with built-in role based authentication via JWT and reusable backend components

193 lines (164 loc) 7.87 kB
'use strict';Object.defineProperty(exports, "__esModule", { value: true });exports.createProcedure = exports.Procedure = undefined; var _preprocess = require('./preprocess'); var _chalk = require('chalk');var _chalk2 = _interopRequireDefault(_chalk); var _log4jsWrapperAdvanced = require('log4js-wrapper-advanced');var _log4jsWrapperAdvanced2 = _interopRequireDefault(_log4jsWrapperAdvanced);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };} const logger = _log4jsWrapperAdvanced2.default.getLogger(); /** * Created by keyvan on 11/27/16. * Enhanced by ronyang on 06/25/19 */class Hook {constructor(functions, neo4jConnection, procedureName, hookName, timeout) { this.timeout = timeout; this.name = hookName; this.procedureName = procedureName; if (!Array.isArray(functions)) if (typeof functions === 'function' || functions instanceof Procedure) functions = [functions];else throw new Error('hook should be function or array of functions'); this.phases = []; this.context = {}; for (let func of functions) { if (func instanceof Procedure) func = createProcedure(neo4jConnection, func);else if (typeof func !== 'function') throw new Error(`element ${func} passed as ${this.procedureName} lifecycle ` + "is neither a 'function' nor a 'procedure'"); this.phases.push(this.asyncify(func)); } this.execute = (...args) => { let next = Promise.resolve(this.phases[0](...args)); const rest = args.slice(1); for (let i = 1; i < this.phases.length; i++) next = Promise.all([this.phases[i], next, rest]). then(([phase, response, rest]) => phase(response, ...rest)); return next; }; } asyncify(func) { return (...args) => Promise.race([ Promise.resolve(func.apply(this.context, args)). then(response => { if (Array.isArray(response)) return Promise.all(response); return response; }), new Promise((resolve, reject) => setTimeout(() => reject( `operation timed out, no response after ${this.timeout / 1000} seconds`), this.timeout))]). catch(error => { const complementary = `, in ${this.name} lifecycle of '${this.procedureName}'`; if (typeof error === 'string') error += complementary;else error.message += complementary; throw error; }); }} class Procedure { constructor({ cypherQueryFile, cypher, timeout = 4000, check = (params, user) => true, preProcess = params => params, postProcess = result => result, postServe = result => result, name = 'procedure', route, globalTransaction = false } = {}) { this.cypherQueryFile = cypherQueryFile; this.cypher = cypher; this.timeout = timeout; this.check = check; this.preProcess = preProcess; this.postProcess = postProcess; this.postServe = postServe; this.name = route || name; this.globalTransaction = globalTransaction; } getMiddleware(neo4jConnection) { const checkHook = new Hook(this.check, neo4jConnection, this.name, 'check', this.timeout); const preProcessHook = new Hook(this.preProcess, neo4jConnection, this.name, 'preProcess', this.timeout); const beginTransaction = (params, ctx) => { logger.trace(_chalk2.default.green('global transaction start!')); ctx.globalTransaction = true; ctx._neo4j_session = neo4jConnection.driver.session(); ctx._neo4j_tx = ctx._neo4j_session.beginTransaction(); }; const endTransaction = async (error, params, ctx) => { if (error) logger.error(_chalk2.default.red(error.toString())); if (ctx.globalTransaction && ctx._neo4j_tx && ctx._neo4j_tx.isOpen()) { if (error) { logger.error(_chalk2.default.red('global transaction rollback!')); await ctx._neo4j_tx.rollback(); } else { logger.trace(_chalk2.default.green('global transaction commit!')); await ctx._neo4j_tx.commit(); } await ctx._neo4j_session.close(); } }; const executionHook = new Hook((params, cypherQueryFile, ctx) => { let result, paramsResult, paramsCypher; if (typeof params.result !== 'undefined') { if (Array.isArray(params.result)) result = Promise.all(params.result);else result = Promise.resolve(params.result); paramsResult = true; } else if (params.cypher || cypherQueryFile) { if (this.globalTransaction) beginTransaction(params, ctx); result = neo4jConnection.executeCypher(params.cypher || cypherQueryFile, params, params.cypher, ctx); paramsCypher = true; } else result = Promise.reject( new Error(`Error in execution lifecycle of ${this.name}, none of ` + "'params.result', 'params.cypher' or 'cypherQueryFile' were present")); return { result, paramsResult, paramsCypher }; }, neo4jConnection, this.name, 'execution', this.timeout); const postProcessHook = new Hook(this.postProcess, neo4jConnection, this.name, 'postProcess', this.timeout); const postServeHook = new Hook(this.postServe, neo4jConnection, this.name, 'postServe', this.timeout * 3); return (params, ctx) => { const response = checkHook.execute(params, ctx). then(checkPassed => { if (!checkPassed) throw new Error(`Check lifecycle hook of ${this.name} did not pass`); return [params, ctx]; }). then(([params, ctx]) => Promise.all([ preProcessHook.execute(params, ctx), ctx])). then(([params, ctx]) => Promise.all([ (0, _preprocess.parseNeo4jInts)('id', 'skip', 'limit')(params), ctx])). then(([params, ctx]) => Promise.all([ executionHook.execute(params, this.cypherQueryFile, ctx), params, ctx])). then(([{ result, paramsResult, paramsCypher }, params, ctx]) => { if (paramsResult) delete params.result; if (paramsCypher) delete params.cypher; return Promise.all([result, params, ctx]); }). then(([result, params, ctx]) => Promise.all([ postProcessHook.execute(result, params, ctx), params, ctx])). then(([result, params, ctx]) => Promise.all([ result, params, ctx, endTransaction(null, params, ctx)])); response. then(([result, params, ctx]) => { return postServeHook.execute(result, params, ctx); }). catch(error => Promise.all([ endTransaction(error, params, ctx)])); return response.then(([result, params, ctx]) => result); }; }} const createProcedure = (neo4jConnection, options) => new Procedure(options).getMiddleware(neo4jConnection);exports. Procedure = Procedure;exports.createProcedure = createProcedure;