UNPKG

alp-node

Version:
174 lines (147 loc) 4.31 kB
import type { IncomingMessage, RequestListener, Server, ServerResponse, } from "node:http"; import path from "node:path"; import Koa from "koa"; import type { DefaultContext, DefaultState, ParameterizedContext } from "koa"; import compress from "koa-compress"; import serve from "koa-static"; import { Logger } from "nightingale-logger"; import type { Router } from "router-segments"; import type { Config } from "./config"; import errors from "./errors"; import type { AlpLanguageContext } from "./language"; import language from "./language"; import _listen from "./listen"; import type { AlpParamsContext, AlpParamsRequest } from "./params/index"; import params from "./params/index"; import type { AlpRouteRef, RouterContext as AlpRouterContext, UrlGenerator, } from "./router"; import type { TranslateBaseContext, TranslateContext } from "./translate/index"; import translate from "./translate/index"; import type { Context as AlpContext, ContextSanitizedState, ContextState, NodeApplication, NodeConfig, } from "./types"; const logger = new Logger("alp"); export interface AlpNodeAppOptions { appDirname: string; packageDirname: string; config: Config & NodeConfig; certPath?: string; publicPath?: string; } declare module "koa" { // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface DefaultState extends ContextState {} interface DefaultContext extends AlpContext, AlpParamsContext, AlpRouterContext, AlpLanguageContext, TranslateContext {} interface BaseContext extends AlpContext, TranslateBaseContext { urlGenerator: UrlGenerator; redirectTo: <P extends Record<string, unknown>>( to: string, params?: P, ) => void; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface BaseRequest extends AlpParamsRequest {} } export class AlpNodeApp extends Koa<DefaultState, DefaultContext> implements NodeApplication { dirname: string; certPath: string; publicPath: string; config: Config & NodeConfig; _server?: Server; router?: Router<any, AlpRouteRef>; /** * @param {Object} [options] * @param {string} [options.certPath] directory of the ssl certificates * @param {string} [options.publicPath] directory of public files */ constructor({ appDirname, packageDirname, config, certPath, publicPath, }: AlpNodeAppOptions) { super(); this.dirname = path.normalize(appDirname); this.certPath = certPath || `${packageDirname}/config/cert`; this.publicPath = publicPath || `${packageDirname}/public/`; this.config = config; this.context.config = this.config; params(this); language(this); translate("locales")(this); this.use(compress()); } existsConfigSync(name: string): ReturnType<Config["existsConfigSync"]> { return this.config.existsConfigSync(name); } loadConfigSync(name: string): ReturnType<Config["loadConfigSync"]> { return this.config.loadConfigSync(name); } override createContext<StateT = DefaultState>( req: IncomingMessage, res: ServerResponse, ): ParameterizedContext<StateT> { const ctx = super.createContext<StateT>(req, res); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions ctx.sanitizedState = {} as ContextSanitizedState; return ctx; } servePublic(): void { this.use(serve(this.publicPath)); // static files } catchErrors(): void { this.use(errors); } override listen(): never { throw new Error("Use start instead"); } /** * Close server and emit close event */ close(): void { if (this._server) { this._server.close(); this.emit("close"); } } async start(fn: () => Promise<void> | void): Promise<Server> { await fn(); try { const server = await _listen( this.config, this.callback() as RequestListener, this.certPath, ); this._server = server; logger.success("started"); if (process.send) process.send("ready"); return server; } catch (error: unknown) { logger.error("start fail", { err: error }); throw error; } } } export type { Context } from "koa"; export { type NodeApplication } from "./types";