@loopback/core
Advanced tools
Comparing version 4.0.0-alpha.14 to 4.0.0-alpha.15
@@ -1,38 +0,8 @@ | ||
/// <reference types="node" /> | ||
import { Binding, Context, Constructor } from '@loopback/context'; | ||
import { OpenApiSpec, OperationObject, RouteEntry } from '.'; | ||
import { ServerRequest, ServerResponse } from 'http'; | ||
import { Context, Binding, Constructor } from '@loopback/context'; | ||
import { Server } from './server'; | ||
import { Component } from './component'; | ||
import { HttpHandler } from './http-handler'; | ||
import { SequenceHandler, SequenceFunction } from './sequence'; | ||
import { ControllerClass } from './router/routing-table'; | ||
export declare class Application extends Context { | ||
options: ApplicationOptions | undefined; | ||
options: ApplicationConfig | undefined; | ||
constructor(options?: ApplicationConfig | undefined); | ||
/** | ||
* Handle incoming HTTP(S) request by invoking the corresponding | ||
* Controller method via the configured Sequence. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
* const app = new Application(); | ||
* // setup controllers, etc. | ||
* | ||
* const server = http.createServer(app.handleHttp); | ||
* server.listen(3000); | ||
* ``` | ||
* | ||
* @param req The request. | ||
* @param res The response. | ||
*/ | ||
handleHttp: (req: ServerRequest, res: ServerResponse) => void; | ||
protected _httpHandler: HttpHandler; | ||
protected readonly httpHandler: HttpHandler; | ||
constructor(options?: ApplicationOptions | undefined); | ||
protected _handleHttpRequest(request: ServerRequest, response: ServerResponse): Promise<void>; | ||
protected _setupHandlerIfNeeded(): void; | ||
private _setupOperation(verb, path, spec); | ||
private _serveOpenApiSpec(request, response, options?); | ||
private _redirectToSwaggerUI(request, response); | ||
/** | ||
* Register a controller class with this application. | ||
@@ -47,3 +17,2 @@ * | ||
* ```ts | ||
* @spec(apiSpec) | ||
* class MyController { | ||
@@ -56,48 +25,72 @@ * } | ||
/** | ||
* Register a new Controller-based route. | ||
* Bind a Server constructor to the Application's master context. | ||
* Each server constructor added in this way must provide a unique prefix | ||
* to prevent binding overlap. | ||
* | ||
* ```ts | ||
* class MyController { | ||
* greet(name: string) { | ||
* return `hello ${name}`; | ||
* } | ||
* } | ||
* app.route('get', '/greet', operationSpec, MyController, 'greet'); | ||
* app.server(RestServer); | ||
* // This server constructor will be bound under "servers.RestServer". | ||
* app.server(RestServer, "v1API"); | ||
* // This server instance will be bound under "servers.v1API". | ||
* ``` | ||
* | ||
* @param verb HTTP verb of the endpoint | ||
* @param path URL path of the endpoint | ||
* @param spec The OpenAPI spec describing the endpoint (operation) | ||
* @param controller Controller constructor | ||
* @param methodName The name of the controller method | ||
* @param {Constructor<Server>} server The server constructor. | ||
* @param {string=} name Optional override for key name. | ||
* @memberof Application | ||
*/ | ||
route(verb: string, path: string, spec: OperationObject, controller: ControllerClass, methodName: string): Binding; | ||
server<T extends Server>(ctor: Constructor<T>, name?: string): void; | ||
/** | ||
* Register a new route. | ||
* Bind an array of Server constructors to the Application's master | ||
* context. | ||
* Each server added in this way will automatically be named based on the | ||
* class constructor name with the "servers." prefix. | ||
* | ||
* If you wish to control the binding keys for particular server instances, | ||
* use the app.server function instead. | ||
* ```ts | ||
* function greet(name: string) { | ||
* return `hello ${name}`; | ||
* } | ||
* const route = new Route('get', '/', operationSpec, greet); | ||
* app.route(route); | ||
* app.servers([ | ||
* RestServer, | ||
* GRPCServer, | ||
* ]); | ||
* // Creates a binding for "servers.RestServer" and a binding for | ||
* // "servers.GRPCServer"; | ||
* ``` | ||
* | ||
* @param route The route to add. | ||
* @param {Constructor<Server>[]} ctors An array of Server constructors. | ||
* @memberof Application | ||
*/ | ||
route(route: RouteEntry): Binding; | ||
api(spec: OpenApiSpec): Binding; | ||
servers<T extends Server>(ctors: Constructor<T>[]): void; | ||
/** | ||
* Get the OpenAPI specification describing the REST API provided by | ||
* this application. | ||
* Retrieve the singleton instance for a bound constructor. | ||
* | ||
* This method merges operations (HTTP endpoints) from the following sources: | ||
* - `app.api(spec)` | ||
* - `app.controller(MyController)` | ||
* - `app.route(route)` | ||
* - `app.route('get', '/greet', operationSpec, MyController, 'greet')` | ||
* @template T | ||
* @param {Constructor<T>=} ctor The constructor that was used to make the | ||
* binding. | ||
* @returns {Promise<T>} | ||
* @memberof Application | ||
*/ | ||
getApiSpec(): OpenApiSpec; | ||
protected _logError(err: Error, statusCode: number, req: ServerRequest): void; | ||
getServer<T extends Server>(target: Constructor<T> | String): Promise<T>; | ||
/** | ||
* Start the application, and all of its registered servers. | ||
* | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
start(): Promise<void>; | ||
/** | ||
* Stop the application instance and all of its registered servers. | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
stop(): Promise<void>; | ||
/** | ||
* Helper function for iterating across all registered server components. | ||
* @protected | ||
* @template T | ||
* @param {(s: Server) => Promise<T>} fn The function to run against all | ||
* registered servers | ||
* @memberof Application | ||
*/ | ||
protected _forEachServer<T>(fn: (s: Server) => Promise<T>): Promise<void>; | ||
/** | ||
* Add a component to this application. | ||
@@ -122,46 +115,10 @@ * | ||
component(component: Constructor<Component>): void; | ||
/** | ||
* Configure a custom sequence class for handling incoming requests. | ||
* | ||
* ```ts | ||
* class MySequence implements SequenceHandler { | ||
* constructor( | ||
* @inject('send) public send: Send)) { | ||
* } | ||
* | ||
* public async handle(request: ParsedRequest, response: ServerResponse) { | ||
* send(response, 'hello world'); | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @param value The sequence to invoke for each incoming request. | ||
*/ | ||
sequence(value: Constructor<SequenceHandler>): void; | ||
/** | ||
* Configure a custom sequence function for handling incoming requests. | ||
* | ||
* ```ts | ||
* app.handler((sequence, request, response) => { | ||
* sequence.send(response, 'hello world'); | ||
* }); | ||
* ``` | ||
* | ||
* @param handlerFn The handler to invoke for each incoming request. | ||
*/ | ||
handler(handlerFn: SequenceFunction): void; | ||
/** | ||
* Start the application (e.g. HTTP/HTTPS servers). | ||
*/ | ||
start(): Promise<void>; | ||
protected _onUnhandledError(req: ServerRequest, res: ServerResponse, err: Error): void; | ||
} | ||
export interface ApplicationOptions { | ||
http?: HttpConfig; | ||
export interface ApplicationConfig { | ||
components?: Array<Constructor<Component>>; | ||
sequence?: Constructor<SequenceHandler>; | ||
servers?: { | ||
[name: string]: Constructor<Server>; | ||
}; | ||
[prop: string]: any; | ||
} | ||
export interface HttpConfig { | ||
port: number; | ||
} | ||
export declare type ControllerClass = Constructor<any>; |
@@ -6,45 +6,6 @@ "use strict"; | ||
// License text available at https://opensource.org/licenses/MIT | ||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
}; | ||
var __metadata = (this && this.__metadata) || function (k, v) { | ||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | ||
}; | ||
var __param = (this && this.__param) || function (paramIndex, decorator) { | ||
return function (target, key) { decorator(target, key, paramIndex); } | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert_1 = require("assert"); | ||
const swagger2openapi = require('swagger2openapi'); | ||
const js_yaml_1 = require("js-yaml"); | ||
const context_1 = require("@loopback/context"); | ||
const _1 = require("."); | ||
const http_1 = require("http"); | ||
const component_1 = require("./component"); | ||
const metadata_1 = require("./router/metadata"); | ||
const http_handler_1 = require("./http-handler"); | ||
const writer_1 = require("./writer"); | ||
const sequence_1 = require("./sequence"); | ||
const reject_1 = require("./router/providers/reject"); | ||
const get_from_context_1 = require("./router/providers/get-from-context"); | ||
const bind_element_1 = require("./router/providers/bind-element"); | ||
const invoke_method_1 = require("./router/providers/invoke-method"); | ||
const find_route_1 = require("./router/providers/find-route"); | ||
const keys_1 = require("./keys"); | ||
const SequenceActions = keys_1.CoreBindings.SequenceActions; | ||
// NOTE(bajtos) we cannot use `import * as cloneDeep from 'lodash/cloneDeep' | ||
// because it produces the following TypeScript error: | ||
// Module '"(...)/node_modules/@types/lodash/cloneDeep/index"' resolves to | ||
// a non-module entity and cannot be imported using this construct. | ||
const cloneDeep = require('lodash/cloneDeep'); | ||
const debug = require('debug')('loopback:core:application'); | ||
const OPENAPI_SPEC_MAPPING = { | ||
'/openapi.json': { version: '3.0.0', format: 'json' }, | ||
'/openapi.yaml': { version: '3.0.0', format: 'yaml' }, | ||
'/swagger.json': { version: '2.0', format: 'json' }, | ||
'/swagger.yaml': { version: '2.0', format: 'yaml' }, | ||
}; | ||
class Application extends context_1.Context { | ||
@@ -56,4 +17,7 @@ constructor(options) { | ||
options = {}; | ||
this.bind(keys_1.CoreBindings.HTTP_PORT).to(options.http ? options.http.port : 3000); | ||
this.api(_1.createEmptyApiSpec()); | ||
// Bind to self to allow injection of application context in other | ||
// modules. | ||
this.bind(keys_1.CoreBindings.APPLICATION_INSTANCE).to(this); | ||
// Make options available to other modules as well. | ||
this.bind(keys_1.CoreBindings.APPLICATION_CONFIG).to(options); | ||
if (options.components) { | ||
@@ -64,137 +28,8 @@ for (const component of options.components) { | ||
} | ||
this.sequence(options.sequence ? options.sequence : sequence_1.DefaultSequence); | ||
this.handleHttp = (req, res) => { | ||
try { | ||
this._handleHttpRequest(req, res) | ||
.catch(err => this._onUnhandledError(req, res, err)); | ||
if (options.servers) { | ||
for (const name in options.servers) { | ||
this.server(options.servers[name], name); | ||
} | ||
catch (err) { | ||
this._onUnhandledError(req, res, err); | ||
} | ||
}; | ||
this.bind(keys_1.CoreBindings.HTTP_HANDLER).toDynamicValue(() => this.httpHandler); | ||
this.bind(SequenceActions.FIND_ROUTE).toProvider(find_route_1.FindRouteProvider); | ||
this.bind(SequenceActions.PARSE_PARAMS).to(_1.parseOperationArgs); | ||
this.bind(SequenceActions.INVOKE_METHOD).toProvider(invoke_method_1.InvokeMethodProvider); | ||
this.bind(SequenceActions.LOG_ERROR).to(this._logError.bind(this)); | ||
this.bind(SequenceActions.SEND).to(writer_1.writeResultToResponse); | ||
this.bind(SequenceActions.REJECT).toProvider(reject_1.RejectProvider); | ||
this.bind(keys_1.CoreBindings.GET_FROM_CONTEXT).toProvider(get_from_context_1.GetFromContextProvider); | ||
this.bind(keys_1.CoreBindings.BIND_ELEMENT).toProvider(bind_element_1.BindElementProvider); | ||
} | ||
get httpHandler() { | ||
this._setupHandlerIfNeeded(); | ||
return this._httpHandler; | ||
} | ||
_handleHttpRequest(request, response) { | ||
// allow CORS support for all endpoints so that users | ||
// can test with online SwaggerUI instance | ||
response.setHeader('Access-Control-Allow-Origin', '*'); | ||
response.setHeader('Access-Control-Allow-Credentials', 'true'); | ||
response.setHeader('Access-Control-Allow-Max-Age', '86400'); | ||
if (request.method === 'GET' && request.url && | ||
request.url in OPENAPI_SPEC_MAPPING) { | ||
// NOTE(bajtos) Regular routes are handled through Sequence. | ||
// IMO, this built-in endpoint should not run through a Sequence, | ||
// because it's not part of the application API itself. | ||
// E.g. if the app implements access/audit logs, I don't want | ||
// this endpoint to trigger a log entry. If the server implements | ||
// content-negotiation to support XML clients, I don't want the OpenAPI | ||
// spec to be converted into an XML response. | ||
const options = OPENAPI_SPEC_MAPPING[request.url]; | ||
return this._serveOpenApiSpec(request, response, options); | ||
} | ||
if (request.method === 'GET' && request.url && | ||
request.url === '/swagger-ui') { | ||
return this._redirectToSwaggerUI(request, response); | ||
} | ||
return this.httpHandler.handleRequest(request, response); | ||
} | ||
_setupHandlerIfNeeded() { | ||
// TODO(bajtos) support hot-reloading of controllers | ||
// after the app started. The idea is to rebuild the HttpHandler | ||
// instance whenever a controller was added/deleted. | ||
// See https://github.com/strongloop/loopback-next/issues/433 | ||
if (this._httpHandler) | ||
return; | ||
this._httpHandler = new http_handler_1.HttpHandler(this); | ||
for (const b of this.find('controllers.*')) { | ||
const controllerName = b.key.replace(/^controllers\./, ''); | ||
const ctor = b.valueConstructor; | ||
if (!ctor) { | ||
throw new Error(`The controller ${controllerName} was not bound via .toClass()`); | ||
} | ||
const apiSpec = metadata_1.getControllerSpec(ctor); | ||
if (!apiSpec) { | ||
// controller methods are specified through app.api() spec | ||
continue; | ||
} | ||
this._httpHandler.registerController(ctor, apiSpec); | ||
} | ||
for (const b of this.find('routes.*')) { | ||
// TODO(bajtos) should we support routes defined asynchronously? | ||
const route = this.getSync(b.key); | ||
this._httpHandler.registerRoute(route); | ||
} | ||
// TODO(bajtos) should we support API spec defined asynchronously? | ||
const spec = this.getSync(keys_1.CoreBindings.API_SPEC); | ||
for (const path in spec.paths) { | ||
for (const verb in spec.paths[path]) { | ||
const routeSpec = spec.paths[path][verb]; | ||
this._setupOperation(verb, path, routeSpec); | ||
} | ||
} | ||
} | ||
_setupOperation(verb, path, spec) { | ||
const handler = spec['x-operation']; | ||
if (typeof handler === 'function') { | ||
// Remove a field value that cannot be represented in JSON. | ||
// Start by creating a shallow-copy of the spec, so that we don't | ||
// modify the original spec object provided by user. | ||
spec = Object.assign({}, spec); | ||
delete spec['x-operation']; | ||
const route = new _1.Route(verb, path, spec, handler); | ||
this._httpHandler.registerRoute(route); | ||
return; | ||
} | ||
const controllerName = spec['x-controller-name']; | ||
if (typeof controllerName === 'string') { | ||
const b = this.find(`controllers.${controllerName}`)[0]; | ||
if (!b) { | ||
throw new Error(`Unknown controller ${controllerName} used by "${verb} ${path}"`); | ||
} | ||
const ctor = b.valueConstructor; | ||
if (!ctor) { | ||
throw new Error(`The controller ${controllerName} was not bound via .toClass()`); | ||
} | ||
const route = new _1.ControllerRoute(verb, path, spec, ctor); | ||
this._httpHandler.registerRoute(route); | ||
return; | ||
} | ||
throw new Error(`There is no handler configured for operation "${verb} ${path}`); | ||
} | ||
async _serveOpenApiSpec(request, response, options) { | ||
options = options || { version: '2.0', format: 'json' }; | ||
let specObj = this.getApiSpec(); | ||
if (options.version === '3.0.0') { | ||
specObj = await swagger2openapi.convertObj(specObj, { direct: true }); | ||
} | ||
if (options.format === 'json') { | ||
const spec = JSON.stringify(specObj, null, 2); | ||
response.setHeader('content-type', 'application/json; charset=utf-8'); | ||
response.end(spec, 'utf-8'); | ||
} | ||
else { | ||
const yaml = js_yaml_1.safeDump(specObj, {}); | ||
response.setHeader('content-type', 'text/yaml; charset=utf-8'); | ||
response.end(yaml, 'utf-8'); | ||
} | ||
} | ||
async _redirectToSwaggerUI(request, response) { | ||
response.statusCode = 308; | ||
response.setHeader('Location', 'http://petstore.swagger.io/?url=' + | ||
'http://' + request.headers.host + | ||
'/swagger.json'); | ||
response.end(); | ||
} | ||
/** | ||
@@ -210,3 +45,2 @@ * Register a controller class with this application. | ||
* ```ts | ||
* @spec(apiSpec) | ||
* class MyController { | ||
@@ -220,53 +54,102 @@ * } | ||
} | ||
route(routeOrVerb, path, spec, controller, methodName) { | ||
if (typeof routeOrVerb === 'object') { | ||
const r = routeOrVerb; | ||
return this.bind(`routes.${r.verb} ${r.path}`).to(r); | ||
/** | ||
* Bind a Server constructor to the Application's master context. | ||
* Each server constructor added in this way must provide a unique prefix | ||
* to prevent binding overlap. | ||
* | ||
* ```ts | ||
* app.server(RestServer); | ||
* // This server constructor will be bound under "servers.RestServer". | ||
* app.server(RestServer, "v1API"); | ||
* // This server instance will be bound under "servers.v1API". | ||
* ``` | ||
* | ||
* @param {Constructor<Server>} server The server constructor. | ||
* @param {string=} name Optional override for key name. | ||
* @memberof Application | ||
*/ | ||
server(ctor, name) { | ||
const suffix = name || ctor.name; | ||
const key = `${keys_1.CoreBindings.SERVERS}.${suffix}`; | ||
this.bind(key) | ||
.toClass(ctor) | ||
.inScope(context_1.BindingScope.SINGLETON); | ||
} | ||
/** | ||
* Bind an array of Server constructors to the Application's master | ||
* context. | ||
* Each server added in this way will automatically be named based on the | ||
* class constructor name with the "servers." prefix. | ||
* | ||
* If you wish to control the binding keys for particular server instances, | ||
* use the app.server function instead. | ||
* ```ts | ||
* app.servers([ | ||
* RestServer, | ||
* GRPCServer, | ||
* ]); | ||
* // Creates a binding for "servers.RestServer" and a binding for | ||
* // "servers.GRPCServer"; | ||
* ``` | ||
* | ||
* @param {Constructor<Server>[]} ctors An array of Server constructors. | ||
* @memberof Application | ||
*/ | ||
servers(ctors) { | ||
ctors.map(ctor => this.server(ctor)); | ||
} | ||
/** | ||
* Retrieve the singleton instance for a bound constructor. | ||
* | ||
* @template T | ||
* @param {Constructor<T>=} ctor The constructor that was used to make the | ||
* binding. | ||
* @returns {Promise<T>} | ||
* @memberof Application | ||
*/ | ||
async getServer(target) { | ||
let key; | ||
// instanceof check not reliable for string. | ||
if (typeof target === 'string') { | ||
key = `${keys_1.CoreBindings.SERVERS}.${target}`; | ||
} | ||
if (!path) { | ||
throw new assert_1.AssertionError({ | ||
message: 'path is required for a controller-based route', | ||
}); | ||
else { | ||
const ctor = target; | ||
key = `servers.${ctor.name}`; | ||
} | ||
if (!spec) { | ||
throw new assert_1.AssertionError({ | ||
message: 'spec is required for a controller-based route', | ||
}); | ||
} | ||
if (!controller) { | ||
throw new assert_1.AssertionError({ | ||
message: 'controller is required for a controller-based route', | ||
}); | ||
} | ||
if (!methodName) { | ||
throw new assert_1.AssertionError({ | ||
message: 'methodName is required for a controller-based route', | ||
}); | ||
} | ||
return this.route(new _1.ControllerRoute(routeOrVerb, path, spec, controller, methodName)); | ||
return (await this.get(key)); | ||
} | ||
api(spec) { | ||
return this.bind(keys_1.CoreBindings.API_SPEC).to(spec); | ||
} | ||
/** | ||
* Get the OpenAPI specification describing the REST API provided by | ||
* this application. | ||
* Start the application, and all of its registered servers. | ||
* | ||
* This method merges operations (HTTP endpoints) from the following sources: | ||
* - `app.api(spec)` | ||
* - `app.controller(MyController)` | ||
* - `app.route(route)` | ||
* - `app.route('get', '/greet', operationSpec, MyController, 'greet')` | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
getApiSpec() { | ||
const spec = this.getSync(keys_1.CoreBindings.API_SPEC); | ||
// Apply deep clone to prevent getApiSpec() callers from | ||
// accidentally modifying our internal routing data | ||
spec.paths = cloneDeep(this.httpHandler.describeApiPaths()); | ||
return spec; | ||
async start() { | ||
await this._forEachServer(s => s.start()); | ||
} | ||
_logError(err, statusCode, req) { | ||
console.error('Unhandled error in %s %s: %s %s', req.method, req.url, statusCode, err.stack || err); | ||
/** | ||
* Stop the application instance and all of its registered servers. | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
async stop() { | ||
await this._forEachServer(s => s.stop()); | ||
} | ||
/** | ||
* Helper function for iterating across all registered server components. | ||
* @protected | ||
* @template T | ||
* @param {(s: Server) => Promise<T>} fn The function to run against all | ||
* registered servers | ||
* @memberof Application | ||
*/ | ||
async _forEachServer(fn) { | ||
const bindings = this.find(`${keys_1.CoreBindings.SERVERS}.*`); | ||
await Promise.all(bindings.map(async (binding) => { | ||
const server = (await this.get(binding.key)); | ||
return await fn(server); | ||
})); | ||
} | ||
/** | ||
* Add a component to this application. | ||
@@ -296,95 +179,4 @@ * | ||
} | ||
/** | ||
* Configure a custom sequence class for handling incoming requests. | ||
* | ||
* ```ts | ||
* class MySequence implements SequenceHandler { | ||
* constructor( | ||
* @inject('send) public send: Send)) { | ||
* } | ||
* | ||
* public async handle(request: ParsedRequest, response: ServerResponse) { | ||
* send(response, 'hello world'); | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @param value The sequence to invoke for each incoming request. | ||
*/ | ||
sequence(value) { | ||
this.bind(keys_1.CoreBindings.SEQUENCE).toClass(value); | ||
} | ||
/** | ||
* Configure a custom sequence function for handling incoming requests. | ||
* | ||
* ```ts | ||
* app.handler((sequence, request, response) => { | ||
* sequence.send(response, 'hello world'); | ||
* }); | ||
* ``` | ||
* | ||
* @param handlerFn The handler to invoke for each incoming request. | ||
*/ | ||
handler(handlerFn) { | ||
let SequenceFromFunction = class SequenceFromFunction extends sequence_1.DefaultSequence { | ||
// NOTE(bajtos) Unfortunately, we have to duplicate the constructor | ||
// in order for our DI/IoC framework to inject constructor arguments | ||
constructor(ctx, findRoute, parseParams, invoke, send, reject) { | ||
super(ctx, findRoute, parseParams, invoke, send, reject); | ||
this.ctx = ctx; | ||
this.findRoute = findRoute; | ||
this.parseParams = parseParams; | ||
this.invoke = invoke; | ||
this.send = send; | ||
this.reject = reject; | ||
} | ||
async handle(request, response) { | ||
await Promise.resolve(handlerFn(this, request, response)); | ||
} | ||
}; | ||
SequenceFromFunction = __decorate([ | ||
__param(0, context_1.inject(keys_1.CoreBindings.Http.CONTEXT)), | ||
__param(1, context_1.inject(SequenceActions.FIND_ROUTE)), | ||
__param(2, context_1.inject(SequenceActions.PARSE_PARAMS)), | ||
__param(3, context_1.inject(SequenceActions.INVOKE_METHOD)), | ||
__param(4, context_1.inject(SequenceActions.SEND)), | ||
__param(5, context_1.inject(SequenceActions.REJECT)), | ||
__metadata("design:paramtypes", [context_1.Context, Function, Function, Function, Function, Function]) | ||
], SequenceFromFunction); | ||
this.sequence(SequenceFromFunction); | ||
} | ||
/** | ||
* Start the application (e.g. HTTP/HTTPS servers). | ||
*/ | ||
async start() { | ||
// Setup the HTTP handler so that we can verify the configuration | ||
// of API spec, controllers and routes at startup time. | ||
this._setupHandlerIfNeeded(); | ||
const httpPort = await this.get(keys_1.CoreBindings.HTTP_PORT); | ||
const server = http_1.createServer(this.handleHttp); | ||
// TODO(bajtos) support httpHostname too | ||
// See https://github.com/strongloop/loopback-next/issues/434 | ||
server.listen(httpPort); | ||
return new Promise((resolve, reject) => { | ||
server.once('listening', () => { | ||
this.bind(keys_1.CoreBindings.HTTP_PORT).to(server.address().port); | ||
resolve(); | ||
}); | ||
server.once('error', reject); | ||
}); | ||
} | ||
_onUnhandledError(req, res, err) { | ||
if (!res.headersSent) { | ||
res.statusCode = 500; | ||
res.end(); | ||
} | ||
// It's the responsibility of the Sequence to handle any errors. | ||
// If an unhandled error escaped, then something very wrong happened | ||
// and it's best to crash the process immediately. | ||
process.nextTick(() => { | ||
throw err; | ||
}); | ||
} | ||
} | ||
exports.Application = Application; | ||
//# sourceMappingURL=application.js.map |
import { Constructor, Provider, BoundValue } from '@loopback/context'; | ||
import { Application } from '.'; | ||
import { Server, Application } from '.'; | ||
export interface ProviderMap { | ||
@@ -9,4 +9,14 @@ [key: string]: Constructor<Provider<BoundValue>>; | ||
providers?: ProviderMap; | ||
servers?: { | ||
[name: string]: Constructor<Server>; | ||
}; | ||
[prop: string]: any; | ||
} | ||
/** | ||
* Mount a component to an Application. | ||
* | ||
* @export | ||
* @param {Application} app | ||
* @param {Component} component | ||
*/ | ||
export declare function mountComponent(app: Application, component: Component): void; |
@@ -7,2 +7,9 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Mount a component to an Application. | ||
* | ||
* @export | ||
* @param {Application} app | ||
* @param {Component} component | ||
*/ | ||
function mountComponent(app, component) { | ||
@@ -19,4 +26,9 @@ if (component.controllers) { | ||
} | ||
if (component.servers) { | ||
for (const serverKey in component.servers) { | ||
app.server(component.servers[serverKey], serverKey); | ||
} | ||
} | ||
} | ||
exports.mountComponent = mountComponent; | ||
//# sourceMappingURL=component.js.map |
@@ -1,17 +0,7 @@ | ||
export { Application } from './application'; | ||
export { Component, ProviderMap } from './component'; | ||
export * from './router/metadata'; | ||
export * from './sequence'; | ||
export { inject } from '@loopback/context'; | ||
export * from '@loopback/openapi-spec'; | ||
export { ServerRequest, ServerResponse } from 'http'; | ||
import * as HttpErrors from 'http-errors'; | ||
export { HttpErrors }; | ||
export { ParsedRequest, OperationRetval, FindRoute, InvokeMethod, LogError, OperationArgs, GetFromContext, BindElement, PathParameterValues, ParseParams, Reject, Send } from './internal-types'; | ||
export { parseOperationArgs } from './parser'; | ||
export { parseRequestUrl } from './router/routing-table'; | ||
export { RouteEntry, RoutingTable, Route, ControllerRoute, ResolvedRoute, createResolvedRoute } from './router/routing-table'; | ||
export { HttpHandler } from './http-handler'; | ||
export { writeResultToResponse } from './writer'; | ||
export { RejectProvider } from './router/providers/reject'; | ||
export { inject, Context } from '@loopback/context'; | ||
export { Server } from './server'; | ||
export * from './application'; | ||
export * from './promisify'; | ||
export * from './component'; | ||
export * from './keys'; |
@@ -10,34 +10,10 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// package dependencies | ||
var application_1 = require("./application"); | ||
exports.Application = application_1.Application; | ||
__export(require("./router/metadata")); | ||
__export(require("./sequence")); | ||
// loopback dependencies | ||
var context_1 = require("@loopback/context"); | ||
exports.inject = context_1.inject; | ||
__export(require("@loopback/openapi-spec")); | ||
// external dependencies | ||
var http_1 = require("http"); | ||
exports.ServerRequest = http_1.ServerRequest; | ||
exports.ServerResponse = http_1.ServerResponse; | ||
// import all errors from external http-errors package | ||
const HttpErrors = require("http-errors"); | ||
exports.HttpErrors = HttpErrors; | ||
var parser_1 = require("./parser"); | ||
exports.parseOperationArgs = parser_1.parseOperationArgs; | ||
var routing_table_1 = require("./router/routing-table"); | ||
exports.parseRequestUrl = routing_table_1.parseRequestUrl; | ||
var routing_table_2 = require("./router/routing-table"); | ||
exports.RoutingTable = routing_table_2.RoutingTable; | ||
exports.Route = routing_table_2.Route; | ||
exports.ControllerRoute = routing_table_2.ControllerRoute; | ||
exports.createResolvedRoute = routing_table_2.createResolvedRoute; | ||
var http_handler_1 = require("./http-handler"); | ||
exports.HttpHandler = http_handler_1.HttpHandler; | ||
var writer_1 = require("./writer"); | ||
exports.writeResultToResponse = writer_1.writeResultToResponse; | ||
var reject_1 = require("./router/providers/reject"); | ||
exports.RejectProvider = reject_1.RejectProvider; | ||
exports.Context = context_1.Context; | ||
__export(require("./application")); | ||
__export(require("./promisify")); | ||
__export(require("./component")); | ||
__export(require("./keys")); | ||
//# sourceMappingURL=index.js.map |
export declare namespace CoreBindings { | ||
const HTTP_PORT = "http.port"; | ||
const HTTP_HANDLER = "http.handler"; | ||
const API_SPEC = "application.apiSpec"; | ||
const SEQUENCE = "sequence"; | ||
namespace SequenceActions { | ||
const FIND_ROUTE = "sequence.actions.findRoute"; | ||
const PARSE_PARAMS = "sequence.actions.parseParams"; | ||
const INVOKE_METHOD = "sequence.actions.invokeMethod"; | ||
const LOG_ERROR = "sequence.actions.logError"; | ||
const SEND = "sequence.actions.send"; | ||
const REJECT = "sequence.actions.reject"; | ||
} | ||
const GET_FROM_CONTEXT = "getFromContext"; | ||
const BIND_ELEMENT = "bindElement"; | ||
const APPLICATION_INSTANCE = "application.instance"; | ||
const APPLICATION_CONFIG = "application.config"; | ||
const SERVERS = "servers"; | ||
const CONTROLLER_CLASS = "controller.current.ctor"; | ||
const CONTROLLER_METHOD_NAME = "controller.current.operation"; | ||
const CONTROLLER_METHOD_META = "controller.method.meta"; | ||
namespace Http { | ||
const REQUEST = "http.request"; | ||
const RESPONSE = "http.response"; | ||
const CONTEXT = "http.request.context"; | ||
} | ||
} |
@@ -9,29 +9,12 @@ "use strict"; | ||
(function (CoreBindings) { | ||
// application-wide bindings | ||
CoreBindings.HTTP_PORT = 'http.port'; | ||
CoreBindings.HTTP_HANDLER = 'http.handler'; | ||
CoreBindings.API_SPEC = 'application.apiSpec'; | ||
CoreBindings.SEQUENCE = 'sequence'; | ||
let SequenceActions; | ||
(function (SequenceActions) { | ||
SequenceActions.FIND_ROUTE = 'sequence.actions.findRoute'; | ||
SequenceActions.PARSE_PARAMS = 'sequence.actions.parseParams'; | ||
SequenceActions.INVOKE_METHOD = 'sequence.actions.invokeMethod'; | ||
SequenceActions.LOG_ERROR = 'sequence.actions.logError'; | ||
SequenceActions.SEND = 'sequence.actions.send'; | ||
SequenceActions.REJECT = 'sequence.actions.reject'; | ||
})(SequenceActions = CoreBindings.SequenceActions || (CoreBindings.SequenceActions = {})); | ||
CoreBindings.GET_FROM_CONTEXT = 'getFromContext'; | ||
CoreBindings.BIND_ELEMENT = 'bindElement'; | ||
// request-specific bindings | ||
// application | ||
CoreBindings.APPLICATION_INSTANCE = 'application.instance'; | ||
CoreBindings.APPLICATION_CONFIG = 'application.config'; | ||
// server | ||
CoreBindings.SERVERS = 'servers'; | ||
// controller | ||
CoreBindings.CONTROLLER_CLASS = 'controller.current.ctor'; | ||
CoreBindings.CONTROLLER_METHOD_NAME = 'controller.current.operation'; | ||
CoreBindings.CONTROLLER_METHOD_META = 'controller.method.meta'; | ||
let Http; | ||
(function (Http) { | ||
Http.REQUEST = 'http.request'; | ||
Http.RESPONSE = 'http.response'; | ||
Http.CONTEXT = 'http.request.context'; | ||
})(Http = CoreBindings.Http || (CoreBindings.Http = {})); | ||
})(CoreBindings = exports.CoreBindings || (exports.CoreBindings = {})); | ||
//# sourceMappingURL=keys.js.map |
@@ -1,38 +0,8 @@ | ||
/// <reference types="node" /> | ||
import { Binding, Context, Constructor } from '@loopback/context'; | ||
import { OpenApiSpec, OperationObject, RouteEntry } from '.'; | ||
import { ServerRequest, ServerResponse } from 'http'; | ||
import { Context, Binding, Constructor } from '@loopback/context'; | ||
import { Server } from './server'; | ||
import { Component } from './component'; | ||
import { HttpHandler } from './http-handler'; | ||
import { SequenceHandler, SequenceFunction } from './sequence'; | ||
import { ControllerClass } from './router/routing-table'; | ||
export declare class Application extends Context { | ||
options: ApplicationOptions | undefined; | ||
options: ApplicationConfig | undefined; | ||
constructor(options?: ApplicationConfig | undefined); | ||
/** | ||
* Handle incoming HTTP(S) request by invoking the corresponding | ||
* Controller method via the configured Sequence. | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
* const app = new Application(); | ||
* // setup controllers, etc. | ||
* | ||
* const server = http.createServer(app.handleHttp); | ||
* server.listen(3000); | ||
* ``` | ||
* | ||
* @param req The request. | ||
* @param res The response. | ||
*/ | ||
handleHttp: (req: ServerRequest, res: ServerResponse) => void; | ||
protected _httpHandler: HttpHandler; | ||
protected readonly httpHandler: HttpHandler; | ||
constructor(options?: ApplicationOptions | undefined); | ||
protected _handleHttpRequest(request: ServerRequest, response: ServerResponse): Promise<void>; | ||
protected _setupHandlerIfNeeded(): void; | ||
private _setupOperation(verb, path, spec); | ||
private _serveOpenApiSpec(request, response, options?); | ||
private _redirectToSwaggerUI(request, response); | ||
/** | ||
* Register a controller class with this application. | ||
@@ -47,3 +17,2 @@ * | ||
* ```ts | ||
* @spec(apiSpec) | ||
* class MyController { | ||
@@ -56,48 +25,72 @@ * } | ||
/** | ||
* Register a new Controller-based route. | ||
* Bind a Server constructor to the Application's master context. | ||
* Each server constructor added in this way must provide a unique prefix | ||
* to prevent binding overlap. | ||
* | ||
* ```ts | ||
* class MyController { | ||
* greet(name: string) { | ||
* return `hello ${name}`; | ||
* } | ||
* } | ||
* app.route('get', '/greet', operationSpec, MyController, 'greet'); | ||
* app.server(RestServer); | ||
* // This server constructor will be bound under "servers.RestServer". | ||
* app.server(RestServer, "v1API"); | ||
* // This server instance will be bound under "servers.v1API". | ||
* ``` | ||
* | ||
* @param verb HTTP verb of the endpoint | ||
* @param path URL path of the endpoint | ||
* @param spec The OpenAPI spec describing the endpoint (operation) | ||
* @param controller Controller constructor | ||
* @param methodName The name of the controller method | ||
* @param {Constructor<Server>} server The server constructor. | ||
* @param {string=} name Optional override for key name. | ||
* @memberof Application | ||
*/ | ||
route(verb: string, path: string, spec: OperationObject, controller: ControllerClass, methodName: string): Binding; | ||
server<T extends Server>(ctor: Constructor<T>, name?: string): void; | ||
/** | ||
* Register a new route. | ||
* Bind an array of Server constructors to the Application's master | ||
* context. | ||
* Each server added in this way will automatically be named based on the | ||
* class constructor name with the "servers." prefix. | ||
* | ||
* If you wish to control the binding keys for particular server instances, | ||
* use the app.server function instead. | ||
* ```ts | ||
* function greet(name: string) { | ||
* return `hello ${name}`; | ||
* } | ||
* const route = new Route('get', '/', operationSpec, greet); | ||
* app.route(route); | ||
* app.servers([ | ||
* RestServer, | ||
* GRPCServer, | ||
* ]); | ||
* // Creates a binding for "servers.RestServer" and a binding for | ||
* // "servers.GRPCServer"; | ||
* ``` | ||
* | ||
* @param route The route to add. | ||
* @param {Constructor<Server>[]} ctors An array of Server constructors. | ||
* @memberof Application | ||
*/ | ||
route(route: RouteEntry): Binding; | ||
api(spec: OpenApiSpec): Binding; | ||
servers<T extends Server>(ctors: Constructor<T>[]): void; | ||
/** | ||
* Get the OpenAPI specification describing the REST API provided by | ||
* this application. | ||
* Retrieve the singleton instance for a bound constructor. | ||
* | ||
* This method merges operations (HTTP endpoints) from the following sources: | ||
* - `app.api(spec)` | ||
* - `app.controller(MyController)` | ||
* - `app.route(route)` | ||
* - `app.route('get', '/greet', operationSpec, MyController, 'greet')` | ||
* @template T | ||
* @param {Constructor<T>=} ctor The constructor that was used to make the | ||
* binding. | ||
* @returns {Promise<T>} | ||
* @memberof Application | ||
*/ | ||
getApiSpec(): OpenApiSpec; | ||
protected _logError(err: Error, statusCode: number, req: ServerRequest): void; | ||
getServer<T extends Server>(target: Constructor<T> | String): Promise<T>; | ||
/** | ||
* Start the application, and all of its registered servers. | ||
* | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
start(): Promise<void>; | ||
/** | ||
* Stop the application instance and all of its registered servers. | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
stop(): Promise<void>; | ||
/** | ||
* Helper function for iterating across all registered server components. | ||
* @protected | ||
* @template T | ||
* @param {(s: Server) => Promise<T>} fn The function to run against all | ||
* registered servers | ||
* @memberof Application | ||
*/ | ||
protected _forEachServer<T>(fn: (s: Server) => Promise<T>): Promise<void>; | ||
/** | ||
* Add a component to this application. | ||
@@ -122,46 +115,10 @@ * | ||
component(component: Constructor<Component>): void; | ||
/** | ||
* Configure a custom sequence class for handling incoming requests. | ||
* | ||
* ```ts | ||
* class MySequence implements SequenceHandler { | ||
* constructor( | ||
* @inject('send) public send: Send)) { | ||
* } | ||
* | ||
* public async handle(request: ParsedRequest, response: ServerResponse) { | ||
* send(response, 'hello world'); | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @param value The sequence to invoke for each incoming request. | ||
*/ | ||
sequence(value: Constructor<SequenceHandler>): void; | ||
/** | ||
* Configure a custom sequence function for handling incoming requests. | ||
* | ||
* ```ts | ||
* app.handler((sequence, request, response) => { | ||
* sequence.send(response, 'hello world'); | ||
* }); | ||
* ``` | ||
* | ||
* @param handlerFn The handler to invoke for each incoming request. | ||
*/ | ||
handler(handlerFn: SequenceFunction): void; | ||
/** | ||
* Start the application (e.g. HTTP/HTTPS servers). | ||
*/ | ||
start(): Promise<void>; | ||
protected _onUnhandledError(req: ServerRequest, res: ServerResponse, err: Error): void; | ||
} | ||
export interface ApplicationOptions { | ||
http?: HttpConfig; | ||
export interface ApplicationConfig { | ||
components?: Array<Constructor<Component>>; | ||
sequence?: Constructor<SequenceHandler>; | ||
servers?: { | ||
[name: string]: Constructor<Server>; | ||
}; | ||
[prop: string]: any; | ||
} | ||
export interface HttpConfig { | ||
port: number; | ||
} | ||
export declare type ControllerClass = Constructor<any>; |
@@ -6,14 +6,2 @@ "use strict"; | ||
// License text available at https://opensource.org/licenses/MIT | ||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
}; | ||
var __metadata = (this && this.__metadata) || function (k, v) { | ||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | ||
}; | ||
var __param = (this && this.__param) || function (paramIndex, decorator) { | ||
return function (target, key) { decorator(target, key, paramIndex); } | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
@@ -28,32 +16,5 @@ return new (P || (P = Promise))(function (resolve, reject) { | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert_1 = require("assert"); | ||
const swagger2openapi = require('swagger2openapi'); | ||
const js_yaml_1 = require("js-yaml"); | ||
const context_1 = require("@loopback/context"); | ||
const _1 = require("."); | ||
const http_1 = require("http"); | ||
const component_1 = require("./component"); | ||
const metadata_1 = require("./router/metadata"); | ||
const http_handler_1 = require("./http-handler"); | ||
const writer_1 = require("./writer"); | ||
const sequence_1 = require("./sequence"); | ||
const reject_1 = require("./router/providers/reject"); | ||
const get_from_context_1 = require("./router/providers/get-from-context"); | ||
const bind_element_1 = require("./router/providers/bind-element"); | ||
const invoke_method_1 = require("./router/providers/invoke-method"); | ||
const find_route_1 = require("./router/providers/find-route"); | ||
const keys_1 = require("./keys"); | ||
const SequenceActions = keys_1.CoreBindings.SequenceActions; | ||
// NOTE(bajtos) we cannot use `import * as cloneDeep from 'lodash/cloneDeep' | ||
// because it produces the following TypeScript error: | ||
// Module '"(...)/node_modules/@types/lodash/cloneDeep/index"' resolves to | ||
// a non-module entity and cannot be imported using this construct. | ||
const cloneDeep = require('lodash/cloneDeep'); | ||
const debug = require('debug')('loopback:core:application'); | ||
const OPENAPI_SPEC_MAPPING = { | ||
'/openapi.json': { version: '3.0.0', format: 'json' }, | ||
'/openapi.yaml': { version: '3.0.0', format: 'yaml' }, | ||
'/swagger.json': { version: '2.0', format: 'json' }, | ||
'/swagger.yaml': { version: '2.0', format: 'yaml' }, | ||
}; | ||
class Application extends context_1.Context { | ||
@@ -65,4 +26,7 @@ constructor(options) { | ||
options = {}; | ||
this.bind(keys_1.CoreBindings.HTTP_PORT).to(options.http ? options.http.port : 3000); | ||
this.api(_1.createEmptyApiSpec()); | ||
// Bind to self to allow injection of application context in other | ||
// modules. | ||
this.bind(keys_1.CoreBindings.APPLICATION_INSTANCE).to(this); | ||
// Make options available to other modules as well. | ||
this.bind(keys_1.CoreBindings.APPLICATION_CONFIG).to(options); | ||
if (options.components) { | ||
@@ -73,141 +37,8 @@ for (const component of options.components) { | ||
} | ||
this.sequence(options.sequence ? options.sequence : sequence_1.DefaultSequence); | ||
this.handleHttp = (req, res) => { | ||
try { | ||
this._handleHttpRequest(req, res) | ||
.catch(err => this._onUnhandledError(req, res, err)); | ||
if (options.servers) { | ||
for (const name in options.servers) { | ||
this.server(options.servers[name], name); | ||
} | ||
catch (err) { | ||
this._onUnhandledError(req, res, err); | ||
} | ||
}; | ||
this.bind(keys_1.CoreBindings.HTTP_HANDLER).toDynamicValue(() => this.httpHandler); | ||
this.bind(SequenceActions.FIND_ROUTE).toProvider(find_route_1.FindRouteProvider); | ||
this.bind(SequenceActions.PARSE_PARAMS).to(_1.parseOperationArgs); | ||
this.bind(SequenceActions.INVOKE_METHOD).toProvider(invoke_method_1.InvokeMethodProvider); | ||
this.bind(SequenceActions.LOG_ERROR).to(this._logError.bind(this)); | ||
this.bind(SequenceActions.SEND).to(writer_1.writeResultToResponse); | ||
this.bind(SequenceActions.REJECT).toProvider(reject_1.RejectProvider); | ||
this.bind(keys_1.CoreBindings.GET_FROM_CONTEXT).toProvider(get_from_context_1.GetFromContextProvider); | ||
this.bind(keys_1.CoreBindings.BIND_ELEMENT).toProvider(bind_element_1.BindElementProvider); | ||
} | ||
get httpHandler() { | ||
this._setupHandlerIfNeeded(); | ||
return this._httpHandler; | ||
} | ||
_handleHttpRequest(request, response) { | ||
// allow CORS support for all endpoints so that users | ||
// can test with online SwaggerUI instance | ||
response.setHeader('Access-Control-Allow-Origin', '*'); | ||
response.setHeader('Access-Control-Allow-Credentials', 'true'); | ||
response.setHeader('Access-Control-Allow-Max-Age', '86400'); | ||
if (request.method === 'GET' && request.url && | ||
request.url in OPENAPI_SPEC_MAPPING) { | ||
// NOTE(bajtos) Regular routes are handled through Sequence. | ||
// IMO, this built-in endpoint should not run through a Sequence, | ||
// because it's not part of the application API itself. | ||
// E.g. if the app implements access/audit logs, I don't want | ||
// this endpoint to trigger a log entry. If the server implements | ||
// content-negotiation to support XML clients, I don't want the OpenAPI | ||
// spec to be converted into an XML response. | ||
const options = OPENAPI_SPEC_MAPPING[request.url]; | ||
return this._serveOpenApiSpec(request, response, options); | ||
} | ||
if (request.method === 'GET' && request.url && | ||
request.url === '/swagger-ui') { | ||
return this._redirectToSwaggerUI(request, response); | ||
} | ||
return this.httpHandler.handleRequest(request, response); | ||
} | ||
_setupHandlerIfNeeded() { | ||
// TODO(bajtos) support hot-reloading of controllers | ||
// after the app started. The idea is to rebuild the HttpHandler | ||
// instance whenever a controller was added/deleted. | ||
// See https://github.com/strongloop/loopback-next/issues/433 | ||
if (this._httpHandler) | ||
return; | ||
this._httpHandler = new http_handler_1.HttpHandler(this); | ||
for (const b of this.find('controllers.*')) { | ||
const controllerName = b.key.replace(/^controllers\./, ''); | ||
const ctor = b.valueConstructor; | ||
if (!ctor) { | ||
throw new Error(`The controller ${controllerName} was not bound via .toClass()`); | ||
} | ||
const apiSpec = metadata_1.getControllerSpec(ctor); | ||
if (!apiSpec) { | ||
// controller methods are specified through app.api() spec | ||
continue; | ||
} | ||
this._httpHandler.registerController(ctor, apiSpec); | ||
} | ||
for (const b of this.find('routes.*')) { | ||
// TODO(bajtos) should we support routes defined asynchronously? | ||
const route = this.getSync(b.key); | ||
this._httpHandler.registerRoute(route); | ||
} | ||
// TODO(bajtos) should we support API spec defined asynchronously? | ||
const spec = this.getSync(keys_1.CoreBindings.API_SPEC); | ||
for (const path in spec.paths) { | ||
for (const verb in spec.paths[path]) { | ||
const routeSpec = spec.paths[path][verb]; | ||
this._setupOperation(verb, path, routeSpec); | ||
} | ||
} | ||
} | ||
_setupOperation(verb, path, spec) { | ||
const handler = spec['x-operation']; | ||
if (typeof handler === 'function') { | ||
// Remove a field value that cannot be represented in JSON. | ||
// Start by creating a shallow-copy of the spec, so that we don't | ||
// modify the original spec object provided by user. | ||
spec = Object.assign({}, spec); | ||
delete spec['x-operation']; | ||
const route = new _1.Route(verb, path, spec, handler); | ||
this._httpHandler.registerRoute(route); | ||
return; | ||
} | ||
const controllerName = spec['x-controller-name']; | ||
if (typeof controllerName === 'string') { | ||
const b = this.find(`controllers.${controllerName}`)[0]; | ||
if (!b) { | ||
throw new Error(`Unknown controller ${controllerName} used by "${verb} ${path}"`); | ||
} | ||
const ctor = b.valueConstructor; | ||
if (!ctor) { | ||
throw new Error(`The controller ${controllerName} was not bound via .toClass()`); | ||
} | ||
const route = new _1.ControllerRoute(verb, path, spec, ctor); | ||
this._httpHandler.registerRoute(route); | ||
return; | ||
} | ||
throw new Error(`There is no handler configured for operation "${verb} ${path}`); | ||
} | ||
_serveOpenApiSpec(request, response, options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
options = options || { version: '2.0', format: 'json' }; | ||
let specObj = this.getApiSpec(); | ||
if (options.version === '3.0.0') { | ||
specObj = yield swagger2openapi.convertObj(specObj, { direct: true }); | ||
} | ||
if (options.format === 'json') { | ||
const spec = JSON.stringify(specObj, null, 2); | ||
response.setHeader('content-type', 'application/json; charset=utf-8'); | ||
response.end(spec, 'utf-8'); | ||
} | ||
else { | ||
const yaml = js_yaml_1.safeDump(specObj, {}); | ||
response.setHeader('content-type', 'text/yaml; charset=utf-8'); | ||
response.end(yaml, 'utf-8'); | ||
} | ||
}); | ||
} | ||
_redirectToSwaggerUI(request, response) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
response.statusCode = 308; | ||
response.setHeader('Location', 'http://petstore.swagger.io/?url=' + | ||
'http://' + request.headers.host + | ||
'/swagger.json'); | ||
response.end(); | ||
}); | ||
} | ||
/** | ||
@@ -223,3 +54,2 @@ * Register a controller class with this application. | ||
* ```ts | ||
* @spec(apiSpec) | ||
* class MyController { | ||
@@ -233,53 +63,110 @@ * } | ||
} | ||
route(routeOrVerb, path, spec, controller, methodName) { | ||
if (typeof routeOrVerb === 'object') { | ||
const r = routeOrVerb; | ||
return this.bind(`routes.${r.verb} ${r.path}`).to(r); | ||
} | ||
if (!path) { | ||
throw new assert_1.AssertionError({ | ||
message: 'path is required for a controller-based route', | ||
}); | ||
} | ||
if (!spec) { | ||
throw new assert_1.AssertionError({ | ||
message: 'spec is required for a controller-based route', | ||
}); | ||
} | ||
if (!controller) { | ||
throw new assert_1.AssertionError({ | ||
message: 'controller is required for a controller-based route', | ||
}); | ||
} | ||
if (!methodName) { | ||
throw new assert_1.AssertionError({ | ||
message: 'methodName is required for a controller-based route', | ||
}); | ||
} | ||
return this.route(new _1.ControllerRoute(routeOrVerb, path, spec, controller, methodName)); | ||
/** | ||
* Bind a Server constructor to the Application's master context. | ||
* Each server constructor added in this way must provide a unique prefix | ||
* to prevent binding overlap. | ||
* | ||
* ```ts | ||
* app.server(RestServer); | ||
* // This server constructor will be bound under "servers.RestServer". | ||
* app.server(RestServer, "v1API"); | ||
* // This server instance will be bound under "servers.v1API". | ||
* ``` | ||
* | ||
* @param {Constructor<Server>} server The server constructor. | ||
* @param {string=} name Optional override for key name. | ||
* @memberof Application | ||
*/ | ||
server(ctor, name) { | ||
const suffix = name || ctor.name; | ||
const key = `${keys_1.CoreBindings.SERVERS}.${suffix}`; | ||
this.bind(key) | ||
.toClass(ctor) | ||
.inScope(context_1.BindingScope.SINGLETON); | ||
} | ||
api(spec) { | ||
return this.bind(keys_1.CoreBindings.API_SPEC).to(spec); | ||
/** | ||
* Bind an array of Server constructors to the Application's master | ||
* context. | ||
* Each server added in this way will automatically be named based on the | ||
* class constructor name with the "servers." prefix. | ||
* | ||
* If you wish to control the binding keys for particular server instances, | ||
* use the app.server function instead. | ||
* ```ts | ||
* app.servers([ | ||
* RestServer, | ||
* GRPCServer, | ||
* ]); | ||
* // Creates a binding for "servers.RestServer" and a binding for | ||
* // "servers.GRPCServer"; | ||
* ``` | ||
* | ||
* @param {Constructor<Server>[]} ctors An array of Server constructors. | ||
* @memberof Application | ||
*/ | ||
servers(ctors) { | ||
ctors.map(ctor => this.server(ctor)); | ||
} | ||
/** | ||
* Get the OpenAPI specification describing the REST API provided by | ||
* this application. | ||
* Retrieve the singleton instance for a bound constructor. | ||
* | ||
* This method merges operations (HTTP endpoints) from the following sources: | ||
* - `app.api(spec)` | ||
* - `app.controller(MyController)` | ||
* - `app.route(route)` | ||
* - `app.route('get', '/greet', operationSpec, MyController, 'greet')` | ||
* @template T | ||
* @param {Constructor<T>=} ctor The constructor that was used to make the | ||
* binding. | ||
* @returns {Promise<T>} | ||
* @memberof Application | ||
*/ | ||
getApiSpec() { | ||
const spec = this.getSync(keys_1.CoreBindings.API_SPEC); | ||
// Apply deep clone to prevent getApiSpec() callers from | ||
// accidentally modifying our internal routing data | ||
spec.paths = cloneDeep(this.httpHandler.describeApiPaths()); | ||
return spec; | ||
getServer(target) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let key; | ||
// instanceof check not reliable for string. | ||
if (typeof target === 'string') { | ||
key = `${keys_1.CoreBindings.SERVERS}.${target}`; | ||
} | ||
else { | ||
const ctor = target; | ||
key = `servers.${ctor.name}`; | ||
} | ||
return (yield this.get(key)); | ||
}); | ||
} | ||
_logError(err, statusCode, req) { | ||
console.error('Unhandled error in %s %s: %s %s', req.method, req.url, statusCode, err.stack || err); | ||
/** | ||
* Start the application, and all of its registered servers. | ||
* | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
start() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this._forEachServer(s => s.start()); | ||
}); | ||
} | ||
/** | ||
* Stop the application instance and all of its registered servers. | ||
* @returns {Promise} | ||
* @memberof Application | ||
*/ | ||
stop() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this._forEachServer(s => s.stop()); | ||
}); | ||
} | ||
/** | ||
* Helper function for iterating across all registered server components. | ||
* @protected | ||
* @template T | ||
* @param {(s: Server) => Promise<T>} fn The function to run against all | ||
* registered servers | ||
* @memberof Application | ||
*/ | ||
_forEachServer(fn) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const bindings = this.find(`${keys_1.CoreBindings.SERVERS}.*`); | ||
yield Promise.all(bindings.map((binding) => __awaiter(this, void 0, void 0, function* () { | ||
const server = (yield this.get(binding.key)); | ||
return yield fn(server); | ||
}))); | ||
}); | ||
} | ||
/** | ||
* Add a component to this application. | ||
@@ -309,99 +196,4 @@ * | ||
} | ||
/** | ||
* Configure a custom sequence class for handling incoming requests. | ||
* | ||
* ```ts | ||
* class MySequence implements SequenceHandler { | ||
* constructor( | ||
* @inject('send) public send: Send)) { | ||
* } | ||
* | ||
* public async handle(request: ParsedRequest, response: ServerResponse) { | ||
* send(response, 'hello world'); | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @param value The sequence to invoke for each incoming request. | ||
*/ | ||
sequence(value) { | ||
this.bind(keys_1.CoreBindings.SEQUENCE).toClass(value); | ||
} | ||
/** | ||
* Configure a custom sequence function for handling incoming requests. | ||
* | ||
* ```ts | ||
* app.handler((sequence, request, response) => { | ||
* sequence.send(response, 'hello world'); | ||
* }); | ||
* ``` | ||
* | ||
* @param handlerFn The handler to invoke for each incoming request. | ||
*/ | ||
handler(handlerFn) { | ||
let SequenceFromFunction = class SequenceFromFunction extends sequence_1.DefaultSequence { | ||
// NOTE(bajtos) Unfortunately, we have to duplicate the constructor | ||
// in order for our DI/IoC framework to inject constructor arguments | ||
constructor(ctx, findRoute, parseParams, invoke, send, reject) { | ||
super(ctx, findRoute, parseParams, invoke, send, reject); | ||
this.ctx = ctx; | ||
this.findRoute = findRoute; | ||
this.parseParams = parseParams; | ||
this.invoke = invoke; | ||
this.send = send; | ||
this.reject = reject; | ||
} | ||
handle(request, response) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield Promise.resolve(handlerFn(this, request, response)); | ||
}); | ||
} | ||
}; | ||
SequenceFromFunction = __decorate([ | ||
__param(0, context_1.inject(keys_1.CoreBindings.Http.CONTEXT)), | ||
__param(1, context_1.inject(SequenceActions.FIND_ROUTE)), | ||
__param(2, context_1.inject(SequenceActions.PARSE_PARAMS)), | ||
__param(3, context_1.inject(SequenceActions.INVOKE_METHOD)), | ||
__param(4, context_1.inject(SequenceActions.SEND)), | ||
__param(5, context_1.inject(SequenceActions.REJECT)), | ||
__metadata("design:paramtypes", [context_1.Context, Function, Function, Function, Function, Function]) | ||
], SequenceFromFunction); | ||
this.sequence(SequenceFromFunction); | ||
} | ||
/** | ||
* Start the application (e.g. HTTP/HTTPS servers). | ||
*/ | ||
start() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Setup the HTTP handler so that we can verify the configuration | ||
// of API spec, controllers and routes at startup time. | ||
this._setupHandlerIfNeeded(); | ||
const httpPort = yield this.get(keys_1.CoreBindings.HTTP_PORT); | ||
const server = http_1.createServer(this.handleHttp); | ||
// TODO(bajtos) support httpHostname too | ||
// See https://github.com/strongloop/loopback-next/issues/434 | ||
server.listen(httpPort); | ||
return new Promise((resolve, reject) => { | ||
server.once('listening', () => { | ||
this.bind(keys_1.CoreBindings.HTTP_PORT).to(server.address().port); | ||
resolve(); | ||
}); | ||
server.once('error', reject); | ||
}); | ||
}); | ||
} | ||
_onUnhandledError(req, res, err) { | ||
if (!res.headersSent) { | ||
res.statusCode = 500; | ||
res.end(); | ||
} | ||
// It's the responsibility of the Sequence to handle any errors. | ||
// If an unhandled error escaped, then something very wrong happened | ||
// and it's best to crash the process immediately. | ||
process.nextTick(() => { | ||
throw err; | ||
}); | ||
} | ||
} | ||
exports.Application = Application; | ||
//# sourceMappingURL=application.js.map |
import { Constructor, Provider, BoundValue } from '@loopback/context'; | ||
import { Application } from '.'; | ||
import { Server, Application } from '.'; | ||
export interface ProviderMap { | ||
@@ -9,4 +9,14 @@ [key: string]: Constructor<Provider<BoundValue>>; | ||
providers?: ProviderMap; | ||
servers?: { | ||
[name: string]: Constructor<Server>; | ||
}; | ||
[prop: string]: any; | ||
} | ||
/** | ||
* Mount a component to an Application. | ||
* | ||
* @export | ||
* @param {Application} app | ||
* @param {Component} component | ||
*/ | ||
export declare function mountComponent(app: Application, component: Component): void; |
@@ -7,2 +7,9 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Mount a component to an Application. | ||
* | ||
* @export | ||
* @param {Application} app | ||
* @param {Component} component | ||
*/ | ||
function mountComponent(app, component) { | ||
@@ -19,4 +26,9 @@ if (component.controllers) { | ||
} | ||
if (component.servers) { | ||
for (const serverKey in component.servers) { | ||
app.server(component.servers[serverKey], serverKey); | ||
} | ||
} | ||
} | ||
exports.mountComponent = mountComponent; | ||
//# sourceMappingURL=component.js.map |
@@ -1,17 +0,7 @@ | ||
export { Application } from './application'; | ||
export { Component, ProviderMap } from './component'; | ||
export * from './router/metadata'; | ||
export * from './sequence'; | ||
export { inject } from '@loopback/context'; | ||
export * from '@loopback/openapi-spec'; | ||
export { ServerRequest, ServerResponse } from 'http'; | ||
import * as HttpErrors from 'http-errors'; | ||
export { HttpErrors }; | ||
export { ParsedRequest, OperationRetval, FindRoute, InvokeMethod, LogError, OperationArgs, GetFromContext, BindElement, PathParameterValues, ParseParams, Reject, Send } from './internal-types'; | ||
export { parseOperationArgs } from './parser'; | ||
export { parseRequestUrl } from './router/routing-table'; | ||
export { RouteEntry, RoutingTable, Route, ControllerRoute, ResolvedRoute, createResolvedRoute } from './router/routing-table'; | ||
export { HttpHandler } from './http-handler'; | ||
export { writeResultToResponse } from './writer'; | ||
export { RejectProvider } from './router/providers/reject'; | ||
export { inject, Context } from '@loopback/context'; | ||
export { Server } from './server'; | ||
export * from './application'; | ||
export * from './promisify'; | ||
export * from './component'; | ||
export * from './keys'; |
@@ -10,34 +10,10 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// package dependencies | ||
var application_1 = require("./application"); | ||
exports.Application = application_1.Application; | ||
__export(require("./router/metadata")); | ||
__export(require("./sequence")); | ||
// loopback dependencies | ||
var context_1 = require("@loopback/context"); | ||
exports.inject = context_1.inject; | ||
__export(require("@loopback/openapi-spec")); | ||
// external dependencies | ||
var http_1 = require("http"); | ||
exports.ServerRequest = http_1.ServerRequest; | ||
exports.ServerResponse = http_1.ServerResponse; | ||
// import all errors from external http-errors package | ||
const HttpErrors = require("http-errors"); | ||
exports.HttpErrors = HttpErrors; | ||
var parser_1 = require("./parser"); | ||
exports.parseOperationArgs = parser_1.parseOperationArgs; | ||
var routing_table_1 = require("./router/routing-table"); | ||
exports.parseRequestUrl = routing_table_1.parseRequestUrl; | ||
var routing_table_2 = require("./router/routing-table"); | ||
exports.RoutingTable = routing_table_2.RoutingTable; | ||
exports.Route = routing_table_2.Route; | ||
exports.ControllerRoute = routing_table_2.ControllerRoute; | ||
exports.createResolvedRoute = routing_table_2.createResolvedRoute; | ||
var http_handler_1 = require("./http-handler"); | ||
exports.HttpHandler = http_handler_1.HttpHandler; | ||
var writer_1 = require("./writer"); | ||
exports.writeResultToResponse = writer_1.writeResultToResponse; | ||
var reject_1 = require("./router/providers/reject"); | ||
exports.RejectProvider = reject_1.RejectProvider; | ||
exports.Context = context_1.Context; | ||
__export(require("./application")); | ||
__export(require("./promisify")); | ||
__export(require("./component")); | ||
__export(require("./keys")); | ||
//# sourceMappingURL=index.js.map |
export declare namespace CoreBindings { | ||
const HTTP_PORT = "http.port"; | ||
const HTTP_HANDLER = "http.handler"; | ||
const API_SPEC = "application.apiSpec"; | ||
const SEQUENCE = "sequence"; | ||
namespace SequenceActions { | ||
const FIND_ROUTE = "sequence.actions.findRoute"; | ||
const PARSE_PARAMS = "sequence.actions.parseParams"; | ||
const INVOKE_METHOD = "sequence.actions.invokeMethod"; | ||
const LOG_ERROR = "sequence.actions.logError"; | ||
const SEND = "sequence.actions.send"; | ||
const REJECT = "sequence.actions.reject"; | ||
} | ||
const GET_FROM_CONTEXT = "getFromContext"; | ||
const BIND_ELEMENT = "bindElement"; | ||
const APPLICATION_INSTANCE = "application.instance"; | ||
const APPLICATION_CONFIG = "application.config"; | ||
const SERVERS = "servers"; | ||
const CONTROLLER_CLASS = "controller.current.ctor"; | ||
const CONTROLLER_METHOD_NAME = "controller.current.operation"; | ||
const CONTROLLER_METHOD_META = "controller.method.meta"; | ||
namespace Http { | ||
const REQUEST = "http.request"; | ||
const RESPONSE = "http.response"; | ||
const CONTEXT = "http.request.context"; | ||
} | ||
} |
@@ -9,29 +9,12 @@ "use strict"; | ||
(function (CoreBindings) { | ||
// application-wide bindings | ||
CoreBindings.HTTP_PORT = 'http.port'; | ||
CoreBindings.HTTP_HANDLER = 'http.handler'; | ||
CoreBindings.API_SPEC = 'application.apiSpec'; | ||
CoreBindings.SEQUENCE = 'sequence'; | ||
let SequenceActions; | ||
(function (SequenceActions) { | ||
SequenceActions.FIND_ROUTE = 'sequence.actions.findRoute'; | ||
SequenceActions.PARSE_PARAMS = 'sequence.actions.parseParams'; | ||
SequenceActions.INVOKE_METHOD = 'sequence.actions.invokeMethod'; | ||
SequenceActions.LOG_ERROR = 'sequence.actions.logError'; | ||
SequenceActions.SEND = 'sequence.actions.send'; | ||
SequenceActions.REJECT = 'sequence.actions.reject'; | ||
})(SequenceActions = CoreBindings.SequenceActions || (CoreBindings.SequenceActions = {})); | ||
CoreBindings.GET_FROM_CONTEXT = 'getFromContext'; | ||
CoreBindings.BIND_ELEMENT = 'bindElement'; | ||
// request-specific bindings | ||
// application | ||
CoreBindings.APPLICATION_INSTANCE = 'application.instance'; | ||
CoreBindings.APPLICATION_CONFIG = 'application.config'; | ||
// server | ||
CoreBindings.SERVERS = 'servers'; | ||
// controller | ||
CoreBindings.CONTROLLER_CLASS = 'controller.current.ctor'; | ||
CoreBindings.CONTROLLER_METHOD_NAME = 'controller.current.operation'; | ||
CoreBindings.CONTROLLER_METHOD_META = 'controller.method.meta'; | ||
let Http; | ||
(function (Http) { | ||
Http.REQUEST = 'http.request'; | ||
Http.RESPONSE = 'http.response'; | ||
Http.CONTEXT = 'http.request.context'; | ||
})(Http = CoreBindings.Http || (CoreBindings.Http = {})); | ||
})(CoreBindings = exports.CoreBindings || (exports.CoreBindings = {})); | ||
//# sourceMappingURL=keys.js.map |
{ | ||
"name": "@loopback/core", | ||
"version": "4.0.0-alpha.14", | ||
"version": "4.0.0-alpha.15", | ||
"description": "", | ||
@@ -23,17 +23,7 @@ "scripts": { | ||
"dependencies": { | ||
"@loopback/context": "^4.0.0-alpha.12", | ||
"@loopback/openapi-spec": "^4.0.0-alpha.10", | ||
"@types/http-errors": "^1.5.34", | ||
"@types/js-yaml": "^3.9.1", | ||
"body": "^5.1.0", | ||
"debug": "^2.6.0", | ||
"http-errors": "^1.6.1", | ||
"js-yaml": "^3.9.1", | ||
"lodash": "^4.17.4", | ||
"path-to-regexp": "^1.7.0", | ||
"swagger2openapi": "^2.6.4" | ||
"@loopback/context": "^4.0.0-alpha.13", | ||
"lodash": "^4.17.4" | ||
}, | ||
"devDependencies": { | ||
"@loopback/openapi-spec-builder": "^4.0.0-alpha.7", | ||
"@loopback/testlab": "^4.0.0-alpha.7" | ||
"@loopback/testlab": "^4.0.0-alpha.8" | ||
}, | ||
@@ -40,0 +30,0 @@ "files": [ |
@@ -20,18 +20,54 @@ # @loopback.core | ||
## Basic use | ||
## Basic Use | ||
```ts | ||
import {Application} from '@loopback/core'; | ||
`@loopback/core` provides the foundation for your LoopBack app, but unlike | ||
previous versions, it no longer contains the implementation for listening | ||
servers. | ||
const app = new Application(); | ||
app.handler((sequence, request, response) => { | ||
sequence.send(response, 'hello world'); | ||
}); | ||
For a typical example of how to create a REST server with your application, | ||
see the [@loopback/rest package.](https://github.com/strongloop/loopback-next/tree/master/packages/rest) | ||
(async function start() { | ||
await app.start(); | ||
console.log(`The app is running on port ${app.getSync('http.port')}`); | ||
})(); | ||
``` | ||
## Advanced Use | ||
Since `@loopback/core` is decoupled from the listening server implementation, | ||
LoopBack applications are now able to work with any components that provide | ||
this functionality. | ||
```ts | ||
// index.ts | ||
import {Application} from '@loopback/core'; | ||
import {RestComponent} from '@loopback/rest'; | ||
import {GrpcComponent} from '@loopback/grpc'; | ||
const app = new Application({ | ||
components: [ | ||
RestComponent, // REST Server | ||
GrpcComponent, // GRPC Server | ||
], | ||
rest: { | ||
port: 3000, | ||
}, | ||
grpc: { | ||
port: 3001, | ||
}, | ||
}); | ||
// Let's retrieve the bound instances of our servers. | ||
const rest = await app.getServer('RestServer'); | ||
const grpc = await app.getServer('GrpcServer'); | ||
// Define all sorts of bindings here to pass configuration or data | ||
// between your server instances, define controllers and datasources for them, | ||
// etc... | ||
(async function start() { | ||
await app.start(); // This automatically spins up all your servers, too! | ||
console.log(`REST server running on port: ${rest.getSync('http.port')}`); | ||
console.log(`GRPC server running on port: ${grpc.getSync('grpc.port')}`); | ||
})(); | ||
``` | ||
In the above example, having a GRPC server mounted on your Application could | ||
enable communication with other GRPC-enabled microservices, allowing things like | ||
dynamic configuration updates. | ||
## Contributions | ||
@@ -38,0 +74,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
2
1
88
4
498275
88
4341
- Removed@types/http-errors@^1.5.34
- Removed@types/js-yaml@^3.9.1
- Removedbody@^5.1.0
- Removeddebug@^2.6.0
- Removedhttp-errors@^1.6.1
- Removedjs-yaml@^3.9.1
- Removedpath-to-regexp@^1.7.0
- Removedswagger2openapi@^2.6.4
- Removed@loopback/openapi-spec@4.0.0-alpha.25(transitive)
- Removed@types/http-errors@1.8.2(transitive)
- Removed@types/js-yaml@3.12.10(transitive)
- Removedajv@5.5.2(transitive)
- Removedansi-regex@2.1.13.0.1(transitive)
- Removedargparse@1.0.10(transitive)
- Removedbody@5.1.0(transitive)
- Removedbytes@1.0.0(transitive)
- Removedcall-me-maybe@1.0.2(transitive)
- Removedcamelcase@4.1.0(transitive)
- Removedcliui@4.1.0(transitive)
- Removedco@4.6.0(transitive)
- Removedcode-point-at@1.1.0(transitive)
- Removedcontinuable-cache@0.3.1(transitive)
- Removedcross-spawn@6.0.6(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddecamelize@1.2.0(transitive)
- Removeddepd@1.1.2(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removederror@7.2.1(transitive)
- Removedes6-promise@3.3.1(transitive)
- Removedesprima@4.0.1(transitive)
- Removedexeca@1.0.0(transitive)
- Removedfast-deep-equal@1.1.0(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfind-up@2.1.0(transitive)
- Removedget-caller-file@1.0.3(transitive)
- Removedget-stream@4.1.0(transitive)
- Removedhttp-errors@1.8.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedinvert-kv@2.0.0(transitive)
- Removedis-fullwidth-code-point@1.0.02.0.0(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisarray@0.0.1(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjs-yaml@3.14.1(transitive)
- Removedjson-schema-traverse@0.3.1(transitive)
- Removedlcid@2.0.0(transitive)
- Removedlocate-path@2.0.0(transitive)
- Removedmap-age-cleaner@0.1.3(transitive)
- Removedmem@4.3.0(transitive)
- Removedmimic-fn@2.1.0(transitive)
- Removedms@2.0.0(transitive)
- Removednice-try@1.0.5(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removednode-readfiles@0.2.0(transitive)
- Removednpm-run-path@2.0.2(transitive)
- Removednumber-is-nan@1.0.1(transitive)
- Removedonce@1.4.0(transitive)
- Removedos-locale@3.1.0(transitive)
- Removedp-defer@1.0.0(transitive)
- Removedp-is-promise@2.1.0(transitive)
- Removedp-limit@1.3.0(transitive)
- Removedp-locate@2.0.0(transitive)
- Removedp-try@1.0.0(transitive)
- Removedpath-exists@3.0.0(transitive)
- Removedpath-key@2.0.1(transitive)
- Removedpath-to-regexp@1.9.0(transitive)
- Removedpump@3.0.2(transitive)
- Removedraw-body@1.1.7(transitive)
- Removedreftools@0.0.20(transitive)
- Removedrequire-directory@2.1.1(transitive)
- Removedrequire-main-filename@1.0.1(transitive)
- Removedsafe-json-parse@1.0.1(transitive)
- Removedsemver@5.7.2(transitive)
- Removedset-blocking@2.0.0(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedshebang-command@1.2.0(transitive)
- Removedshebang-regex@1.0.0(transitive)
- Removedshould@13.2.3(transitive)
- Removedshould-equal@2.0.0(transitive)
- Removedshould-format@3.0.3(transitive)
- Removedshould-type@1.4.0(transitive)
- Removedshould-type-adaptors@1.1.0(transitive)
- Removedshould-util@1.0.1(transitive)
- Removedsignal-exit@3.0.7(transitive)
- Removedsprintf-js@1.0.3(transitive)
- Removedstatuses@1.5.0(transitive)
- Removedstring-template@0.2.1(transitive)
- Removedstring-width@1.0.22.1.1(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedstrip-ansi@3.0.14.0.0(transitive)
- Removedstrip-eof@1.0.0(transitive)
- Removedswagger2openapi@2.11.16(transitive)
- Removedtoidentifier@1.0.1(transitive)
- Removedtr46@0.0.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)
- Removedwhich@1.3.1(transitive)
- Removedwhich-module@2.0.1(transitive)
- Removedwrap-ansi@2.1.0(transitive)
- Removedwrappy@1.0.2(transitive)
- Removedy18n@3.2.2(transitive)
- Removedyargs@11.1.1(transitive)
- Removedyargs-parser@9.0.2(transitive)