UNPKG

@crumbjs/core

Version:

<img src="https://raw.githubusercontent.com/tuplescompany/crumbjs/refs/heads/main/logo/crumbjs.png" alt="CrumbJS Logo" width="200"/> - The tasty way to build fast apis.

140 lines (139 loc) 5.77 kB
import { buildPath, getModeLogLevel } from './helpers/utils'; import { openapi } from './openapi/openapi'; import { config } from './config'; import { Processor } from './processor/processor'; import { logger } from './helpers/logger'; import { createClientSpecs } from './client-generator'; import { autoCompleteRouteConfig } from './helpers/route-config'; /** * Builds an Bun Http server from your main App */ export class Router { app; startAt; constructor(app) { this.app = app; this.startAt = performance.now(); } /** * Merge global specific defined middlewares and the root App instance middlewares * Both applies to all api routes */ getGlobalMiddlewares() { const globals = Object.values(this.app.getGlobalMiddlewares()); const rootapp = this.app.getMiddlewares(); globals.push(...rootapp); return globals; } async buildRoute(route) { const fullPath = buildPath(...route.pathParts); // RouteDinamic if ('handler' in route) { return { path: fullPath, method: route.method, handler: (request, server) => { return new Processor(request, server, route.config, this.getGlobalMiddlewares(), // served app middlewares are global scope route.handler, config.get('errorHandler')).execute(); }, routeConfig: autoCompleteRouteConfig(route.config), isStatic: false, }; } // RouteStatic const content = route.content instanceof Blob ? await route.content.bytes() : route.content; const contentType = route.contentType ?? (route.content instanceof Blob ? route.content.type : 'application/octet-stream'); return { path: fullPath, method: 'GET', // @ts-expect-error handler: new Response(content, { headers: { 'Content-Type': contentType, }, }), routeConfig: { hide: true }, isStatic: true, }; } /** Ensure title/description/version are present (#config-backed defaults). */ getOpenapiSpecs() { const specs = openapi.getSpec(); if (!specs.info.title) openapi.title(config.get('openapiTitle')); if (!specs.info.description) openapi.description(config.get('openapiDescription')); if (!specs.info.version) openapi.version(config.get('version')); return specs; } /** * Takes all application routes and create Bun.serve compatible Handlers with all request life-cycle throught Processor */ async buildRoutes() { const { withOpenapi, openapiBasePath, openapiUi } = config.all; let dynamics = {}; let statics = {}; for (const route of this.app.getRoutes()) { const buildedRoute = await this.buildRoute(route); const { path, method, handler, routeConfig, isStatic } = buildedRoute; if (isStatic) { statics[path] = handler; } else { if (!dynamics[path]) dynamics[path] = {}; dynamics[path][method] = handler; } // Register openapi route if is enabled and not specifically hide on the route if (withOpenapi && !routeConfig.hide) { openapi.addBuildedRoute(buildedRoute); } const diff = isStatic ? ' (static)' : ''; logger.debug(`🌐 ${method} ${path} Registered${diff}`); } /** * Add openapi routes if is enabled as raw-serve-route, no middlewares / request lifecycle attached */ if (withOpenapi) { const specs = this.getOpenapiSpecs(); const documentPath = buildPath(openapiBasePath, '/doc.json'); const openapiUiPath = buildPath(openapiBasePath); statics[documentPath] = Response.json(specs); statics[openapiUiPath] = new Response(openapi[openapiUi](documentPath)); logger.debug(`📘 GET ${documentPath} Registered (static)`); logger.debug(`📘 GET ${openapiUiPath} Registered (static)`); } const openapiReadyMessage = withOpenapi ? `enabled, UI: ${openapiUi}` : 'disabled'; logger.debug(`📘 OPENAPI: ${openapiReadyMessage}`); if (withOpenapi && config.get('mode') === 'development' && config.get('generateClientSchema')) { await createClientSpecs(openapi.getJson()); logger.debug(`📘 CLIENT: Generated client specification`); } return { statics, dynamics }; } async serve(options) { if (options) config.merge(options); // set level to the global Logger instance on server starts const logLevel = getModeLogLevel(config.get('mode')); logger.setLevel(logLevel); for (const [triggerName, trigger] of Object.entries(this.app.getStartupTriggers())) { logger.debug(`🛠️ Executing on-start '${triggerName}' trigger`); await trigger(); } const routes = await this.buildRoutes(); const server = Bun.serve({ port: config.get('port'), routes: { ...routes.dynamics, ...routes.statics, }, fetch: config.get('notFoundHandler'), }); const duration = performance.now() - this.startAt; logger.debug(`⚡ Startup time: ${duration.toFixed(2)} ms`); logger.debug(`🔌 HTTP Server listening on port ${config.get('port')}`); return server; } }