@layr/component-koa-middleware
Version:
A Koa middleware for your Layr components
121 lines • 5.6 kB
JavaScript
;
/**
* @module component-koa-middleware
*
* A [Koa](https://koajs.com/) middleware allowing to serve a root [`Component`](https://layrjs.com/docs/v2/reference/component) so it can be accessed by a [`ComponentHTTPClient`](https://layrjs.com/docs/v2/reference/component-http-client).
*
* #### Usage
*
* Call the [`serveComponent()`](https://layrjs.com/docs/v2/reference/component-koa-middleware#serve-component-function) function to create a middleware for your Koa app.
*
* **Example:**
*
* ```
* import Koa from 'koa';
* import {Component} from '@layr/component';
* import {serveComponent} from '@layr/component-koa-middleware';
*
* class Movie extends Component {
* // ...
* }
*
* const app = new Koa();
*
* // Serve the `Movie` component at the root ('/')
* app.use(serveComponent(Movie));
*
* app.listen(3210);
* ```
*
* If you want to serve your component at a specific URL, you can use [`koa-mount`](https://github.com/koajs/mount):
*
* ```
* import mount from 'koa-mount';
*
* // Serve the `Movie` component at a specific URL ('/api')
* app.use(mount('/api', serveComponent(Movie)));
* ```
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.serveComponent = void 0;
const tslib_1 = require("tslib");
const component_server_1 = require("@layr/component-server");
const routable_1 = require("@layr/routable");
const raw_body_1 = tslib_1.__importDefault(require("raw-body"));
const mime_types_1 = tslib_1.__importDefault(require("mime-types"));
const sleep_promise_1 = tslib_1.__importDefault(require("sleep-promise"));
const DEFAULT_LIMIT = '8mb';
/**
* Creates a [Koa](https://koajs.com/) middleware exposing the specified root [`Component`](https://layrjs.com/docs/v2/reference/component) class.
*
* @param componentOrComponentServer The root [`Component`](https://layrjs.com/docs/v2/reference/component) class to serve. An instance of a [`ComponentServer`](https://layrjs.com/docs/v2/reference/component-server) will be created under the hood. Alternatively, you can pass an existing instance of a [`ComponentServer`](https://layrjs.com/docs/v2/reference/component-server).
* @param [options.version] A number specifying the version of the created [`ComponentServer`](https://layrjs.com/docs/v2/reference/component-server) (default: `undefined`).
*
* @returns A Koa middleware.
*
* @category Functions
*/
function serveComponent(componentOrComponentServer, options = {}) {
const componentServer = (0, component_server_1.ensureComponentServer)(componentOrComponentServer, options);
const component = componentServer.getComponent();
const routableComponent = (0, routable_1.isRoutableClass)(component) ? component : undefined;
const { limit = DEFAULT_LIMIT, delay = 0, errorRate = 0 } = options;
return async function (ctx) {
if (delay > 0) {
await (0, sleep_promise_1.default)(delay);
}
if (errorRate > 0) {
const threshold = errorRate / 100;
if (Math.random() < threshold) {
throw new Error('A simulated error occurred while handling a component server query');
}
}
const method = ctx.request.method;
const url = ctx.request.url;
const headers = ctx.request.headers ?? {};
const contentType = headers['content-type'] || 'application/octet-stream';
const charset = mime_types_1.default.charset(contentType) || undefined;
const body = await (0, raw_body_1.default)(ctx.req, { limit, encoding: charset });
if (url === '/') {
if (method === 'GET') {
ctx.response.body = await componentServer.receive({ query: { 'introspect=>': { '()': [] } } });
return;
}
if (method === 'POST') {
let parsedBody;
if (typeof body !== 'string') {
throw new Error(`Expected a body of type 'string', but received a value of type '${typeof body}'`);
}
try {
parsedBody = JSON.parse(body);
}
catch (error) {
throw new Error(`An error occurred while parsing a JSON body string ('${body}')`);
}
const { query, components, version } = parsedBody;
ctx.response.body = await componentServer.receive({ query, components, version });
return;
}
ctx.throw(405);
}
if (routableComponent !== undefined) {
const routableComponentFork = routableComponent.fork();
await routableComponentFork.initialize();
const routeResponse = await (0, routable_1.callRouteByURL)(routableComponentFork, url, { method, headers, body });
if (typeof routeResponse?.status !== 'number') {
throw new Error(`Unexpected response \`${JSON.stringify(routeResponse)}\` returned by a component route (a proper response should be an object of the shape \`{status: number; headers?: Record<string, string>; body?: string | Buffer;}\`)`);
}
ctx.response.status = routeResponse.status;
if (routeResponse.headers !== undefined) {
ctx.response.set(routeResponse.headers);
}
if (routeResponse.body !== undefined) {
ctx.response.body = routeResponse.body;
}
return;
}
ctx.throw(404);
};
}
exports.serveComponent = serveComponent;
//# sourceMappingURL=component-koa-middleware.js.map