Skip to content

Commit

Permalink
Merge pull request #10 from zgid123/express-runner-feat/resolve-ioc-f…
Browse files Browse the repository at this point in the history
…or-controller

[express-runner]: resolve ioc
  • Loading branch information
zgid123 authored Jul 1, 2024
2 parents 51cacbb + 941aa02 commit 0334a65
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 38 deletions.
13 changes: 9 additions & 4 deletions packages/@runners/express/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
{
"name": "@abyss.ts/express-runner",
"version": "0.0.1",
"version": "0.0.2",
"author": "Alpha",
"repository": {
"type": "git",
"url": "git+https://github.com/zgid123/abyss-ts.git"
},
"license": "MIT",
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
"main": "lib/index.js",
"types": "lib/index.d.ts"
},
"keywords": [
"abyss",
"abyssts",
Expand All @@ -27,7 +31,8 @@
"@abyss.ts/core": "workspace:^",
"body-parser": "^1.20.2",
"detect-port": "^1.6.1",
"express": "^4.19.2"
"express": "^4.19.2",
"nanoid": "^5.0.7"
},
"devDependencies": {
"@types/body-parser": "^1.19.5",
Expand Down
48 changes: 16 additions & 32 deletions packages/@runners/express/src/Application.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { nanoid } from 'nanoid';
import bodyParser from 'body-parser';
import detectPort from 'detect-port';
import express, { Router, type Express } from 'express';
import express, { type Express } from 'express';
import { AbyssApplication } from '@abyss.ts/core';
import { createServer as createHttpServer } from 'http';
import {
combine,
AbyssApplication,
getControllerMetadata,
getControllerActionMetadata,
} from '@abyss.ts/core';

import { mapParameters } from './utils/actionUtils';

import { mapRoutes } from './utils/routeUtils';
import { mapInjections } from './utils/injectionUtils';
import { mapController } from './utils/controllerUtils';

export class ExpressApplication extends AbyssApplication<ExpressApplication> {
Expand Down Expand Up @@ -40,32 +37,19 @@ export class ExpressApplication extends AbyssApplication<ExpressApplication> {
}

public async run(): Promise<void> {
const controllers = await mapController();

const routes: string[] = [];
const router = Router();
this.#express.use((req, _res, next) => {
Object.assign(req, {
executionId: nanoid(),
});

for (const controller of controllers) {
const { route } = getControllerMetadata(controller);
const actions = getControllerActionMetadata(controller);
next();
});

for (const action of actions) {
const { exec, httpMethod, route: actionRoute, propertyKey } = action;
const httpRoute = `/${combine({ joinWith: '/' }, route, actionRoute)}`;

routes.push(httpRoute);
const controllers = await mapController();

router[httpMethod](httpRoute, (req, res) => {
const params = mapParameters({
controller,
propertyKey,
request: req,
});
mapInjections();

res.send(exec(...params));
});
}
}
const [routes, router] = mapRoutes(controllers);

this.#express.use(router);

Expand All @@ -81,7 +65,7 @@ export class ExpressApplication extends AbyssApplication<ExpressApplication> {
console.log(`Server is running on ${runningPort}`);

if (routes.length) {
console.log(`\n\nList routes: \n\n${routes.join('\n')}`);
console.log(`\n\nList routes: \n${routes.join('\n')}`);
}
}
}
15 changes: 14 additions & 1 deletion packages/@runners/express/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export { Controller, Get, Query, Body, Request, Param } from '@abyss.ts/core';
export {
Get,
Put,
Body,
Post,
Patch,
Query,
Param,
Delete,
Inject,
Request,
Controller,
Injectable,
} from '@abyss.ts/core';

export type { Request as TRequest } from 'express';

Expand Down
8 changes: 7 additions & 1 deletion packages/@runners/express/src/utils/controllerUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { getControllerMetadata, loadControllers } from '@abyss.ts/core';

export async function mapController(): Promise<TAny[]> {
console.log('Scanning controllers...');

const importedControllers = await loadControllers();

return importedControllers.reduce((result, module) => {
const controllers = importedControllers.reduce((result, module) => {
const classes = Object.values(module);

for (const klass of classes) {
Expand All @@ -18,4 +20,8 @@ export async function mapController(): Promise<TAny[]> {

return result;
}, []);

console.log('Finish scanning controllers!\n');

return controllers;
}
109 changes: 109 additions & 0 deletions packages/@runners/express/src/utils/injectionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
pushToIoCContainer,
getFromIoCContainer,
getInjectionMetadata,
getInjectParamMetadata,
type IInjectionProps,
} from '@abyss.ts/core';

export function mapInjections(): void {
console.log('Scanning injections...');

const injections = getInjectionMetadata();
const groupedInjections = topologicalGroup(injections);

for (const pack of groupedInjections) {
for (const injection of pack) {
const { target, scope } = injection;

if (scope === 'singleton') {
const metadata = getInjectParamMetadata({
target,
});

const params = metadata.map(({ extractor }) => {
return getFromIoCContainer(extractor);
});

const instance = new target(...params);

pushToIoCContainer({
target,
instance,
});
}
}
}

console.log('Finish scanning injections!\n');
}

export function topologicalGroup(
injections: Array<IInjectionProps | null>,
): IInjectionProps[][] {
const visited: Record<TAny, { visited: true; level: number }> = {};
const result: IInjectionProps[][] = [];

while (injections.length) {
const removedIndexes = [];

for (let i = 0, n = injections.length; i < n; i++) {
const injection = injections[i];

if (!injection) {
removedIndexes.push(i);
continue;
}

const { target } = injection;

const params = getInjectParamMetadata({
target,
});

if (!params.length) {
result[0] ||= [];
result[0].push(injection);
removedIndexes.push(i);

visited[target] = {
level: 0,
visited: true,
};

continue;
}

let level = 0;
let visitedAllDependencies = true;

params.forEach((injectable) => {
const { extractor } = injectable;
const visitedData = visited[extractor];

if (visitedData) {
level = Math.max(level, visitedData.level);
} else {
visitedAllDependencies = false;
}
});

if (visitedAllDependencies) {
result[level + 1] ||= [];
result[level + 1]!.push(injection);
removedIndexes.push(i);

visited[target] = {
visited: true,
level: level + 1,
};
}
}

removedIndexes.reverse().forEach((index) => {
injections.splice(index, 1);
});
}

return result;
}
52 changes: 52 additions & 0 deletions packages/@runners/express/src/utils/routeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Router } from 'express';
import {
combine,
getFromIoCContainer,
getControllerMetadata,
getInjectParamMetadata,
getControllerActionMetadata,
} from '@abyss.ts/core';

import { mapParameters } from './actionUtils';

export function mapRoutes(controllers: TAny[]): [string[], Router] {
console.log('Initializing routes...');

const routes: string[] = [];
const router = Router();

for (const controller of controllers) {
const { route } = getControllerMetadata(controller);
const actions = getControllerActionMetadata(controller);
const injects = getInjectParamMetadata({ target: controller });

const injections = injects.map(({ extractor }) => {
return getFromIoCContainer(extractor);
});

const controllerInstance = new controller(...injections);

for (const action of actions) {
const { exec, httpMethod, route: actionRoute, propertyKey } = action;
const httpRoute = `/${combine({ joinWith: '/' }, route, actionRoute)}`;

routes.push(
combine({ joinWith: ' ' }, httpMethod.toUpperCase(), httpRoute),
);

router[httpMethod](httpRoute, (req, res) => {
const params = mapParameters({
controller,
propertyKey,
request: req,
});

res.send(exec.bind(controllerInstance)(...params));
});
}
}

console.log('Finish initializing routes!\n');

return [routes, router];
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0334a65

Please sign in to comment.