@ffsm/napi
Version:
Napi - A framework using with Next.js for building APIs.
174 lines (173 loc) • 6.14 kB
JavaScript
import { sync } from "./helpers/sync";
import { NapiMethod } from "./enums";
import { NapiRouter } from "./router";
import { matchRoute } from "./helpers/match-route";
import { NapiResponse } from "./response";
import { NapiController } from "./controller";
import { NapiContext } from "./context";
import { NapiConfiguration } from "./configuration";
export class NapiApplication {
constructor(options) {
this.options = options;
this.prefix = "";
this.route = "";
this.params = {}; // Detech from route
if (!NapiApplication.controllers) {
NapiApplication.controllers = new Map();
}
}
getRequest() {
return this.req;
}
getRouter() {
if (!this.router) {
this.initRouter();
}
return this.router;
}
getPayload() {
return this.payload;
}
getController() {
return this.controller.getInstance();
}
getConfig(name) {
return this.configuration.get(name);
}
getResponse() {
if (!this.res) {
this.initResponse();
}
return this.res;
}
get method() {
return this.req.method.toUpperCase();
}
createExcutionContext() {
return {
getRequest: this.getRequest.bind(this),
getRouter: this.getRouter.bind(this),
getPayload: this.getPayload.bind(this),
getController: this.getController.bind(this),
getConfig: this.getConfig.bind(this),
getResponse: this.getResponse.bind(this),
};
}
async initializeConfiguration() {
var _a;
this.configuration = await NapiConfiguration.initialize((_a = this.options.configs) !== null && _a !== void 0 ? _a : {});
}
async extractPrefix() {
if (!this.options.prefix) {
return this;
}
const prefix = this.options.prefix;
if (typeof prefix === "function") {
this.prefix = await prefix(this.configuration);
}
else if (typeof prefix === "string") {
this.prefix = prefix;
}
}
async extractRoute() {
const params = await this.payload.params;
if (!params) {
this.route = "";
return this;
}
const route = Object.values(params)[0];
this.route = route ? route.join("/") : "";
return this;
}
initRouter() {
if (this.router) {
return this;
}
this.router = new NapiRouter(this.req, this.route, this.params);
return this;
}
async loadController() {
var _a;
const controllers = (_a = this.options.controllers) !== null && _a !== void 0 ? _a : [];
if (!controllers.length) {
return;
}
for (let index = 0; index < controllers.length; index++) {
const controller = controllers[index];
if (!NapiController.has(controller)) {
continue;
}
const instance = NapiController.get(controller);
const name = instance.getName();
if (!instance.hasPrefix()) {
throw new Error(`Please using decorator on ${name}`);
}
const prefix = instance.getPrefix();
const controllerRoute = `/${this.prefix}/${prefix}/`.replace(/\/+/g, "/");
const requestRoute = `/${this.route}/`.replace(/\/+/g, "/");
if (!requestRoute.startsWith(controllerRoute)) {
continue;
}
this.controller = instance;
break;
}
}
loadMethods() {
if (!this.controller) {
return;
}
if (!this.controller.hasRoutes()) {
// TODO: throw error no routes decorated
return;
}
const routes = this.controller.getRoutes();
const routeKeys = Array.from(routes.keys());
for (let index = 0; index < routeKeys.length; index++) {
const methodName = routeKeys[index];
const [method, ...remain] = methodName.split(":");
if (method.toUpperCase() !== this.method) {
continue;
}
const route = `/${this.prefix}/${this.controller.getPrefix()}/${remain.join(":")}`.replace(/\/+/g, "/");
const params = matchRoute(route, this.route);
if (!params) {
continue;
}
this.controllerMethod = routes.get(methodName);
this.params = params;
}
}
async loadParameters() {
this.context = new NapiContext(this.prefix, this.controller, this.createExcutionContext(), this.params);
this.params = (await this.context.execute(this.route)).getRouteParams();
}
initResponse() {
this.res = new NapiResponse(this.controller, this.context, this.createExcutionContext());
}
async execute() {
if (!this.controllerMethod) {
return new Response(null);
}
const instance = this.controller.getInstance();
const method = instance[this.controllerMethod];
const params = this.context.getDecoratorParams();
const result = await method.bind(instance)(...params);
return this.res.send(result);
}
async dispatch(req, payload) {
this.req = req;
this.payload = payload;
await sync([this.initializeConfiguration.bind(this)], [this.extractPrefix.bind(this)], [this.extractRoute.bind(this)], [this.loadController.bind(this)], [this.loadMethods.bind(this)], [this.loadParameters.bind(this)], [this.initRouter.bind(this)], [this.initResponse.bind(this)]);
return await this.execute();
}
static register(options) {
return Object
.values(NapiMethod)
.filter((method) => typeof method === "string")
.reduce((acc, method) => {
const upper = method.toUpperCase();
acc[upper] = (req, payload) => (new NapiApplication(options).dispatch(req, payload));
return acc;
}, {});
}
}