UNPKG

nextjs-middleware-chain

Version:
338 lines (272 loc) 12.2 kB
import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _classCallCheck from '@babel/runtime/helpers/classCallCheck'; import _regeneratorRuntime from '@babel/runtime/regenerator'; /** * This will not create a cryptographic quality GUID, but is sufficient * for identifying separate instances of `Middleware` if necessary. * * @returns a guid-like string */ /* eslint-disable */ function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8; return v.toString(16); }); } /* eslint-enable */ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } /** * @typedef MiddlewareOptions * @type {object} * @property {bool} useChainOrder Should chained functions be run in chain order (vs initial creation order). Default: TRUE * @property {bool} useAsyncMiddleware Should middleware functions be able to run asynchronously? Default: FALSE */ var DEFAULT_OPTIONS = { useChainOrder: true, useAsyncMiddleware: true, reqPropName: 'nmc', onMiddlewareStart: function onMiddlewareStart() {}, onMiddlewareComplete: function onMiddlewareComplete() {}, onRouteComplete: function onRouteComplete() {} }; var Middleware = function Middleware(fnsArray, globalOptions, inlineOptions) { var _this2 = this; _classCallCheck(this, Middleware); this.options = _objectSpread(_objectSpread(_objectSpread({}, DEFAULT_OPTIONS), globalOptions), inlineOptions); this.run = []; this.id = uuidv4(); this.finish = function finish(finalFunc, finalFuncName) { var _this = this; // The run array may have null values depending on the chain & configured options. this.run = this.run.filter(function (fn) { return !!fn; }); var id = this.id; var friendlyName = finalFuncName || finalFunc.name; var runnableMiddleware = /*#__PURE__*/function () { var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(pReq, pRes) { var type, res, req, context, runIndex, RUNNER_STATES, runnerState, runNext, result, finalReturnFn; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: // If the passed response object is undefined we can // infer that this was called from a SSR route. type = pRes ? 'api' : 'ssr'; // in SSR the pReq will actually be the `context` object // containing both the `req` and `res` objects, so // `pRes` will be undefined. res = pRes; req = pReq; context = {}; if (!pRes) { res = pReq.res; req = pReq.req; context = pReq; } req[_this.options.reqPropName] = { id: id, name: friendlyName, type: type, context: context }; runIndex = 0; RUNNER_STATES = { running: 'running', ended: 'ended', escaped: 'escaped', handled: 'handled', completed: 'completed' }; runnerState = RUNNER_STATES.running; /** * This will handle state updates resulting from chain functions * and any pre-complete return values * * @param {*} arg * @param {*} payload * @returns */ runNext = function runNext(arg, payload) { var returnedArg = (arg === null || arg === void 0 ? void 0 : arg.toLowerCase()) || ''; if (RUNNER_STATES.running) { switch (returnedArg) { case 'end': runnerState = RUNNER_STATES.ended; return payload; case 'route': runnerState = RUNNER_STATES.completed; return true; default: if (runIndex === _this.run.length - 1) { runnerState = RUNNER_STATES.completed; } return true; } } return false; }; _this.options.onMiddlewareStart(req); case 11: if (!(runnerState === RUNNER_STATES.running && runIndex < _this.run.length)) { _context2.next = 28; break; } result = void 0; if (_this.options.useAsyncMiddleware) { _context2.next = 17; break; } result = _this.run[runIndex](req, res, runNext); _context2.next = 20; break; case 17: _context2.next = 19; return _this.run[runIndex](req, res, runNext); case 19: result = _context2.sent; case 20: if (!(!result || runnerState !== RUNNER_STATES.running && runnerState !== RUNNER_STATES.completed || result.redirect)) { _context2.next = 25; break; } runnerState = RUNNER_STATES.ended; // Short-circuit-path exit of all middleware functionality // console.debug('Short-circuit-path middleware exit (IMPLEMENT FINAL CALLBACK)') _this.options.onMiddlewareComplete(req); _this.options.onRouteComplete(req); return _context2.abrupt("return", result); case 25: runIndex += 1; _context2.next = 11; break; case 28: if (!(runnerState === RUNNER_STATES.completed)) { _context2.next = 33; break; } if (res.finished) { console.warn('WARHING: Response is finished! Did you really mean to `return next()` after finishing the response?'); } finalReturnFn = /*#__PURE__*/function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { var finalReturn; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!(type === 'api')) { _context.next = 6; break; } _context.next = 3; return finalFunc(req, res); case 3: finalReturn = _context.sent; _context.next = 9; break; case 6: _context.next = 8; return finalFunc(context); case 8: finalReturn = _context.sent; case 9: // Happy-path exit of all middleware functionality // console.debug('Happy-path middleware exit (IMPLEMENT FINAL CALLBACK)') _this.options.onRouteComplete(req); return _context.abrupt("return", finalReturn); case 11: case "end": return _context.stop(); } } }, _callee); })); return function finalReturnFn() { return _ref2.apply(this, arguments); }; }(); _this.options.onMiddlewareComplete(req); return _context2.abrupt("return", finalReturnFn()); case 33: // Unknown-path exit of all middleware functionality // console.debig('Unknown-path middleware exit (IMPLEMENT FINAL CALLBACK)') _this.options.onMiddlewareComplete(req); _this.options.onRouteComplete(req); return _context2.abrupt("return", undefined); case 36: case "end": return _context2.stop(); } } }, _callee2); })); return function runnableMiddleware(_x, _x2) { return _ref.apply(this, arguments); }; }(); return runnableMiddleware; }; // This will be run when there is no existing instance of // middleware for a given route. Depending on whether // `useChainOrder` is true, functions will be added to the // end of the `run` array, or inserted into the `run` array // in the order they are present in the `fnsArray` array. fnsArray.forEach(function (fn, i) { var fnName = fn.name; _this2.run.push(null); // eslint-disable-next-line func-names _this2[fnName] = function () { if (this.options.useChainOrder) { this.run.push(fnsArray[i]); } else { this.run[i] = fnsArray[i]; } return this; }; }); }; /** * This is a factory function factory function. * * @param {*} fnsArray * @param {*} globalOptions * * @returns A factory function used to create new instances of the Middleware class */ var middlewareFactoryFactory = function middlewareFactoryFactory(fnsArray, globalOptions) { // Inline options will overwrite global options return function (inlineOptions) { return new Middleware(fnsArray, globalOptions, inlineOptions); }; }; /** * This is run once on creation. It return a factory function that * is, itself, a factory function for creating `Middleware` instances. * * @param {array} functionsArray A collection of functions * @param {MiddlewareOptions} globalOptionsObject An object options * * @returns */ var createMiddleware = function createMiddleware(fnsArray) { var globalOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!(fnsArray !== null && fnsArray !== void 0 && fnsArray.length)) { throw new Error('A functions array must be provided as the first argument.'); } Object.values(fnsArray).forEach(function (fn) { if (typeof fn !== 'function') { throw new Error('Items in the functions array must be a function.'); } if (!fn.name) { throw new Error('Items in the functions array must be named functions.'); } }); Object.keys(globalOptions).forEach(function (key) { if (!DEFAULT_OPTIONS[key]) { console.warn("Unknown option ".concat(key, " provided. Ignoring.")); } }); return middlewareFactoryFactory(fnsArray, globalOptions); }; export { createMiddleware };