@loopback/express
Version:
Integrate with Express and expose middleware infrastructure for sequence and interceptors
289 lines • 12 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
// Node module: @loopback/express
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.toExpressMiddleware = exports.invokeExpressMiddleware = exports.MiddlewareView = exports.invokeMiddleware = exports.createMiddlewareBinding = exports.registerMiddleware = exports.asMiddleware = exports.registerExpressMiddleware = exports.createMiddleware = exports.toMiddleware = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const debug_1 = tslib_1.__importDefault(require("debug"));
const group_sorter_1 = require("./group-sorter");
const keys_1 = require("./keys");
const middleware_interceptor_1 = require("./middleware-interceptor");
const types_1 = require("./types");
const debug = (0, debug_1.default)('loopback:middleware');
/**
* An adapter function to create a LoopBack middleware that invokes the list
* of Express middleware handler functions in the order of their positions
* @example
* ```ts
* toMiddleware(fn);
* toMiddleware(fn1, fn2, fn3);
* ```
* @param firstHandler - An Express middleware handler
* @param additionalHandlers A list of Express middleware handler functions
* @returns A LoopBack middleware function that wraps the list of Express
* middleware
*/
function toMiddleware(firstHandler, ...additionalHandlers) {
if (additionalHandlers.length === 0)
return (0, middleware_interceptor_1.toInterceptor)(firstHandler);
const handlers = [firstHandler, ...additionalHandlers];
const middlewareList = handlers.map(handler => (0, middleware_interceptor_1.toInterceptor)(handler));
return (middlewareCtx, next) => {
if (middlewareList.length === 1) {
return middlewareList[0](middlewareCtx, next);
}
const middlewareChain = new types_1.MiddlewareChain(middlewareCtx, middlewareList);
return middlewareChain.invokeInterceptors(next);
};
}
exports.toMiddleware = toMiddleware;
/**
* An adapter function to create a LoopBack middleware from Express middleware
* factory function and configuration object.
*
* @param middlewareFactory - Express middleware factory function
* @param middlewareConfig - Express middleware config
*
* @returns A LoopBack middleware function that wraps the Express middleware
*/
function createMiddleware(middlewareFactory, middlewareConfig) {
return (0, middleware_interceptor_1.createInterceptor)(middlewareFactory, middlewareConfig);
}
exports.createMiddleware = createMiddleware;
/**
* Bind a Express middleware to the given context
*
* @param ctx - Context object
* @param middlewareFactory - Middleware module name or factory function
* @param middlewareConfig - Middleware config
* @param options - Options for registration
*
* @typeParam CFG - Configuration type
*/
function registerExpressMiddleware(ctx, middlewareFactory, middlewareConfig, options = {}) {
var _a;
options = { injectConfiguration: true, ...options };
options.chain = (_a = options.chain) !== null && _a !== void 0 ? _a : types_1.DEFAULT_MIDDLEWARE_CHAIN;
if (!options.injectConfiguration) {
const middleware = createMiddleware(middlewareFactory, middlewareConfig);
return registerMiddleware(ctx, middleware, options);
}
const providerClass = (0, middleware_interceptor_1.defineInterceptorProvider)(middlewareFactory, middlewareConfig, options);
return registerMiddleware(ctx, providerClass, options);
}
exports.registerExpressMiddleware = registerExpressMiddleware;
/**
* Template function for middleware bindings
* @param options - Options to configure the binding
*/
function asMiddleware(options = {}) {
return function middlewareBindingTemplate(binding) {
var _a, _b;
binding.apply((0, core_1.extensionFor)((_a = options.chain) !== null && _a !== void 0 ? _a : types_1.DEFAULT_MIDDLEWARE_CHAIN));
if (!binding.tagMap.group) {
binding.tag({ group: (_b = options.group) !== null && _b !== void 0 ? _b : keys_1.DEFAULT_MIDDLEWARE_GROUP });
}
const groupsBefore = options.upstreamGroups;
if (groupsBefore != null) {
binding.tag({
upstreamGroups: typeof groupsBefore === 'string' ? [groupsBefore] : groupsBefore,
});
}
const groupsAfter = options.downstreamGroups;
if (groupsAfter != null) {
binding.tag({
downstreamGroups: typeof groupsAfter === 'string' ? [groupsAfter] : groupsAfter,
});
}
};
}
exports.asMiddleware = asMiddleware;
/**
* Bind the middleware function or provider class to the context
* @param ctx - Context object
* @param middleware - Middleware function or provider class
* @param options - Middleware binding options
*/
function registerMiddleware(ctx, middleware, options) {
var _a;
if ((0, core_1.isProviderClass)(middleware)) {
const binding = createMiddlewareBinding(middleware, options);
ctx.add(binding);
return binding;
}
const key = (_a = options.key) !== null && _a !== void 0 ? _a : core_1.BindingKey.generate(keys_1.MIDDLEWARE_NAMESPACE);
return ctx
.bind(key)
.to(middleware)
.apply(asMiddleware(options));
}
exports.registerMiddleware = registerMiddleware;
/**
* Create a binding for the middleware provider class
*
* @param middlewareProviderClass - Middleware provider class
* @param options - Options to create middleware binding
*
*/
function createMiddlewareBinding(middlewareProviderClass, options = {}) {
var _a;
options.chain = (_a = options.chain) !== null && _a !== void 0 ? _a : types_1.DEFAULT_MIDDLEWARE_CHAIN;
const binding = (0, core_1.createBindingFromClass)(middlewareProviderClass, {
defaultScope: core_1.BindingScope.TRANSIENT,
namespace: keys_1.MIDDLEWARE_NAMESPACE,
key: options.key,
}).apply(asMiddleware(options));
return binding;
}
exports.createMiddlewareBinding = createMiddlewareBinding;
/**
* Discover and invoke registered middleware in a chain for the given extension
* point.
*
* @param middlewareCtx - Middleware context
* @param options - Options to invoke the middleware chain
*/
function invokeMiddleware(middlewareCtx, options) {
debug('Invoke middleware chain for %s %s with options', middlewareCtx.request.method, middlewareCtx.request.originalUrl, options);
let keys = options === null || options === void 0 ? void 0 : options.middlewareList;
if (keys == null) {
const view = new MiddlewareView(middlewareCtx, options);
keys = view.middlewareBindingKeys;
view.close();
}
const mwChain = new types_1.MiddlewareChain(middlewareCtx, keys);
return mwChain.invokeInterceptors(options === null || options === void 0 ? void 0 : options.next);
}
exports.invokeMiddleware = invokeMiddleware;
/**
* Watch middleware binding keys for the given context and sort them by
* group
* @param ctx - Context object
* @param options - Middleware options
*/
class MiddlewareView extends core_1.ContextView {
constructor(ctx, options) {
var _a;
// Find extensions for the given extension point binding
const filter = (0, core_1.extensionFilter)((_a = options === null || options === void 0 ? void 0 : options.chain) !== null && _a !== void 0 ? _a : types_1.DEFAULT_MIDDLEWARE_CHAIN);
super(ctx, filter);
this.options = {
chain: types_1.DEFAULT_MIDDLEWARE_CHAIN,
orderedGroups: [],
...options,
};
this.buildMiddlewareKeys();
this.open();
}
refresh() {
super.refresh();
this.buildMiddlewareKeys();
}
/**
* A list of binding keys sorted by group for registered middleware
*/
get middlewareBindingKeys() {
return this.keys;
}
buildMiddlewareKeys() {
var _a;
const middlewareBindings = this.bindings;
if (debug.enabled) {
debug('Middleware for extension point "%s":', this.options.chain, middlewareBindings.map(b => b.key));
}
// Calculate orders from middleware dependencies
const ordersFromDependencies = [];
middlewareBindings.forEach(b => {
var _a, _b, _c;
const group = (_a = b.tagMap.group) !== null && _a !== void 0 ? _a : keys_1.DEFAULT_MIDDLEWARE_GROUP;
const groupsBefore = (_b = b.tagMap.upstreamGroups) !== null && _b !== void 0 ? _b : [];
groupsBefore.forEach(d => ordersFromDependencies.push([d, group]));
const groupsAfter = (_c = b.tagMap.downstreamGroups) !== null && _c !== void 0 ? _c : [];
groupsAfter.forEach(d => ordersFromDependencies.push([group, d]));
});
const order = (0, group_sorter_1.sortListOfGroups)(...ordersFromDependencies, this.options.orderedGroups);
/**
* Validate sorted groups
*/
if (typeof ((_a = this.options) === null || _a === void 0 ? void 0 : _a.validate) === 'function') {
this.options.validate(order);
}
this.keys = middlewareBindings
.sort((0, core_1.compareBindingsByTag)('group', order))
.map(b => b.key);
}
}
exports.MiddlewareView = MiddlewareView;
/**
* Invoke a list of Express middleware handler functions
*
* @example
* ```ts
* import cors from 'cors';
* import helmet from 'helmet';
* import morgan from 'morgan';
* import {MiddlewareContext, invokeExpressMiddleware} from '@loopback/express';
*
* // ... Either an instance of `MiddlewareContext` is passed in or a new one
* // can be instantiated from Express request and response objects
*
* const middlewareCtx = new MiddlewareContext(request, response);
* const finished = await invokeExpressMiddleware(
* middlewareCtx,
* cors(),
* helmet(),
* morgan('combined'));
*
* if (finished) {
* // Http response is sent by one of the middleware
* } else {
* // Http response is yet to be produced
* }
* ```
* @param middlewareCtx - Middleware context
* @param handlers - A list of Express middleware handler functions
*/
function invokeExpressMiddleware(middlewareCtx, ...handlers) {
if (handlers.length === 0) {
throw new Error('No Express middleware handler function is provided.');
}
const middleware = toMiddleware(handlers[0], ...handlers.slice(1));
debug('Invoke Express middleware for %s %s', middlewareCtx.request.method, middlewareCtx.request.originalUrl);
// Invoke the middleware with a no-op next()
const result = middleware(middlewareCtx, () => undefined);
// Check if the response is finished
return (0, core_1.transformValueOrPromise)(result, val => val === middlewareCtx.response);
}
exports.invokeExpressMiddleware = invokeExpressMiddleware;
/**
* An adapter function to create an Express middleware handler to discover and
* invoke registered LoopBack-style middleware in the context.
* @param ctx - Context object to discover registered middleware
*/
function toExpressMiddleware(ctx) {
return (req, res, next) => {
const middlewareCtx = new types_1.MiddlewareContext(req, res, ctx);
new Promise((resolve, reject) => {
// eslint-disable-next-line no-void
void (async () => {
try {
const result = await invokeMiddleware(middlewareCtx);
resolve(result);
}
catch (err) {
reject(err);
}
})();
})
.then(result => {
if (result !== res)
next();
})
.catch(next);
};
}
exports.toExpressMiddleware = toExpressMiddleware;
//# sourceMappingURL=middleware.js.map
;