Do not use this please.
Overseer is a Typescript Aspect-Oriented framework for backend which takes inspiration from Spring-boot and Angular.
Before you begin, make sure your development environment includes Node.js® and an npm package manager.
Overseer requires Node.js version 8.x or 10.x.
- To check your version, run node -v in a terminal/console window.
- To get Node.js, go to nodejs.org.
Creating a Overseer project is as simple as:
npx @jeaks03/typescript-base -o
The npx @jeaks03/typescript-base
part creates a base for a typescript project and the -o
flag lets the installer know that it is an Overseer framework and makes the proper adjustments.
index.ts
import { Overseer } from '@jeaks03/overseer-core';
Overseer.serve(module, 8000);
my.controller.ts
import { Requisite, Pathway } from '@jeaks03/overseer-core';
@Requisite
export class MyController {
@Pathway({ path:'/hello' })
sayHello() {
return {
message: 'hello world!'
}
}
}
This example opens a http server
that listens on port 8000
and registers a GET
endpoint /hello
that returns the "hello world" message.
To start the application run:
npm run dev
- The
Overseer.serve(8000)
line lets the framework know where the sources root is located, and what the port desired port is. @Requisite
makes transforms the class into an injectable and lets the framework find it. More on injectables and this decorator later.@Pathway
marks the method as a handler for the given path. The method is called when the endpoint is reached. More on this later.
Section in which I explain how the framework functions.
For the framework to work correctly it must have it's directory structure as follows:
- index.ts -- doesn't matter where it is. Preferably in
/src
- resources -- ( directory ) it must be one level above index.ts file. So if your main file is
/src/index.ts
then the resources directory must be/resources/
. - public -- ( directory ) it must be inside the resources directory.
/resources/public
In the resources directory can be stored any kind of project files you need and access them freely. Inside it is the public directory where all the files are visible to the http server.
Let's say that you have a file named index.html inside and the server open on port 8000. If you make a request on localhost:8000/index.html
the file will be sent.
Note: If the file is named index.html
then the file will be available on both localhost:8000/index.html
and localhost:8000/
Documentation details regarding the decorators.
This decorator is used to mark the class as an injectable. Yes, Overseer also handles dependency injection in a manner similar to Angular's. In order to inject a requisite it must be a parameter for the constructor.
In order to let the framework find the requisites, all files that contain such classes must have their name ending in
.controller.ts
.service.ts
.component.ts
Example of dependency injection:
my.service.ts
import { Requisite } from '@jeaks03/overseer-core';
@Requisite
export class MyService {
public log(message: string): void {
console.log(message);
}
}
my-other.service.ts
import { Requisite } from '@jeaks03/overseer-core';
import MyService from './my.service';
@Requisite
export class MyOtherService {
constructor(private myService: MyService) {}
private onInit(): void {
this.myService.log('I got initialized!');
}
}
This decorator marks a method as the handler of the given path. It requires an argument of type WayDetails
which has the following attributes:
- path -- string: the path for the endpoint to map. Default:
/
- method -- string: http method. Default:
GET
- statusCode -- number: http status code. Default:
200
- produces -- string[]: list of content types that can be produced by this handler. Default:
['application/json']
- consumes -- string[]: list of content types that can be consumed by this handler. Default:
['application/json', 'multipart/form-data', 'application/x-www-form-urlencoded']
- guards -- Guard[]: list of
Guard
implementations. This works just like Angular's guard security. Default:[]
This decorator is used on requisites to mark a method as a lifecycle event. These events are triggered at a certain time during their life.
It accepts a string as a sort of event type to let it know when to trigger the method. These arguments are:
- "onInit" -- method is triggered shortly after the instantiation
- "afterInit" -- method is triggered after all requisites have been instantiated
Shorthand version of @LifecycleEvent('onInit')
Shorthand version of @LifecycleEvent('afterInit')
This class handles and contains all the requisites.
An instance of this class can be imported under the name of Requisites
as a RestrictedRequisiteManager
interface.
RestrictedRequisiteManager:
interface RestrictedRequisiteManager {
addInstance: (instance, isController?: boolean) => void;
find: <T>(clazz: Class<T>) => T;
findByName: <T>(className: string) => T;
findAll: <T>(clazz: Class<T>) => T[];
}
In order to secure your application, you must provide an implementation of this class as a requisite. The default authentication
implementation provided is NoAuthentication
which basically behaves as if there is no security.
In order to create an instance for any Authentication
implementation you have to pass a UserProvider
to the constructor.
UserProvider
interface is a function that looks like this: (username: string) => UserDetails | Promise<UserDetails>
Overseer ships with the following implementations:
- NoAuthentication
- BasicAuthentication
- JWTAuthentication
Here is an example of how to secure your application using basic auth:
export class SecurityComponent {
constructor(private database: DatabasePlaceHolder) { }
@OnInit()
secureApp() {
const auth = new BasicAuthentication((username: string) => this.database.findUser(username));
Requisites.addInstance(auth);
}
}
This class can be extended to create converters that understand other content types, and it looks like this:
export class Converter {
public getContentType(): string;
public canRead(target: string, contentType: string): boolean;
public canWrite(target: any, contentType: string): boolean;
public doWrite(target: any): string;
public doRead(target: string): any;
}