module import type { Component } from '@layr/component' ; import { ensureComponentServer } from '@layr/component-server' ; import type { ComponentServer , ComponentServerOptions } from '@layr/component-server' ; import { callRouteByURL , isRoutableClass } from '@layr/routable' ; import type { Request , Response } from 'express' ; import getRawBody from 'raw-body' ; import mime from 'mime-types' ; import httpError from 'http-errors' ; import sleep from 'sleep-promise' ; const DEFAULT_LIMIT = '8mb' ; export type ServeComponentOptions = ComponentServerOptions & { limit ? : number | string ; delay ? : number ; errorRate ? : number ; } ; param componentOrComponentServer param options . version returns category export function serveComponent ( componentOrComponentServer , options = { } ) { const componentServer = ensureComponentServer ( componentOrComponentServer , options ) ; const component = componentServer . getComponent ( ) ; const routableComponent = isRoutableClass ( component ) ? component : undefined ; const { limit = DEFAULT_LIMIT , delay = 0 , errorRate = 0 } = options ; return async function ( req , res ) { if ( delay > 0 ) { await sleep ( delay ) ; } if ( errorRate > 0 ) { const threshold = errorRate / 100 ; if ( Math . random ( ) < threshold ) { throw httpError ( 500 , 'A simulated error occurred while handling a component server query' ) ; } } const method = req . method ; const url = req . url ; const headers = ( req . headers as Record < string , string > ) ?? { } ; const contentType = headers [ 'content-type' ] || 'application/octet-stream' ; const charset = mime . charset ( contentType ) || undefined ; const body = await getRawBody ( req , { limit , encoding : charset } ) ; if ( url === '/' ) { if ( method === 'GET' ) { res . json ( 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 ; res . json ( await componentServer . receive ( { query , components , version } ) ) ; return ; } throw httpError ( 405 ) ; } if ( routableComponent !== undefined ) { const routableComponentFork = routableComponent . fork ( ) ; await routableComponentFork . initialize ( ) ; const routeResponse = await 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;}\`)` ) ; } res . status ( routeResponse . status ) ; if ( routeResponse . headers !== undefined ) { res . set ( routeResponse . headers ) ; } if ( routeResponse . body !== undefined ) { res . send ( routeResponse . body ) ; } else { res . end ( ) ; } return ; } throw httpError ( 404 ) ; } ; }