@plumier/core
Version:
Delightful Node.js Rest Framework
523 lines (522 loc) • 18.4 kB
TypeScript
/// <reference types="node" />
import { SetOption } from "cookies";
import { Server } from "http";
import Koa, { Context } from "koa";
import { ClassReflection, MethodReflection, ParameterReflection, PropertyReflection, Class } from "@plumier/reflect";
import { Result, VisitorInvocation } from "@plumier/validator";
import { HttpStatus } from "./http-status";
export declare type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export declare type KeyOf<T> = Extract<keyof T, string>;
export interface ApplyToOption {
/**
* Apply decorator into specific action, only work on controller scoped decorator.
*
* Should specify a correct action name(s)
*/
applyTo?: string | string[];
}
export interface JwtClaims {
[key: string]: any;
}
export interface HttpCookie {
key: string;
value?: string;
option?: SetOption;
}
export declare class ActionResult {
body?: any;
status?: number | undefined;
headers: {
[key: string]: string | string[];
};
cookies: {
key: string;
value?: string;
option?: SetOption;
}[];
constructor(body?: any, status?: number | undefined);
static fromContext(ctx: Context): ActionResult;
setHeader(key: string, value: string | string[]): this;
setStatus(status: number): this;
setCookie(cookie: HttpCookie): this;
setCookie(cookie: HttpCookie[]): this;
setCookie(key: string, value?: string, option?: SetOption): this;
execute(ctx: Context): Promise<void>;
}
export declare class RedirectActionResult extends ActionResult {
path: string;
constructor(path: string);
execute(ctx: Context): Promise<void>;
}
export declare type HttpMethod = "post" | "get" | "put" | "delete" | "patch" | "head" | "trace" | "options";
export declare type RouteMetadata = RouteInfo | VirtualRoute;
export interface RouteInfo {
kind: "ActionRoute";
group?: string;
url: string;
method: HttpMethod;
action: MethodReflection;
controller: ClassReflection;
access?: string;
paramMapper: {
alias: (name: string) => string;
};
}
export interface VirtualRoute {
kind: "VirtualRoute";
group?: string;
url: string;
method: HttpMethod;
provider: Class;
access?: string;
openApiOperation?: any;
}
export interface RouteAnalyzerIssue {
type: "error" | "warning" | "success";
message?: string;
}
export declare type RouteAnalyzerFunction = (route: RouteMetadata, allRoutes: RouteMetadata[]) => RouteAnalyzerIssue[];
export interface Facility {
generateRoutes(app: Readonly<PlumierApplication>, routes: RouteMetadata[]): Promise<RouteMetadata[]>;
setup(app: Readonly<PlumierApplication>): void;
preInitialize(app: Readonly<PlumierApplication>): Promise<void>;
initialize(app: Readonly<PlumierApplication>, routes: RouteMetadata[]): Promise<void>;
}
export declare class DefaultFacility implements Facility {
generateRoutes(app: Readonly<PlumierApplication>, routes: RouteMetadata[]): Promise<RouteMetadata[]>;
setup(app: Readonly<PlumierApplication>): void;
preInitialize(app: Readonly<PlumierApplication>): Promise<void>;
initialize(app: Readonly<PlumierApplication>, routes: RouteMetadata[]): Promise<void>;
}
declare module "koa" {
interface Context {
route?: Readonly<RouteInfo>;
routes: RouteInfo[];
config: Readonly<Configuration>;
user?: JwtClaims;
}
interface Request {
addQuery(query: any): void;
}
interface DefaultState {
caller: "system" | "invoke";
user?: JwtClaims;
}
}
export interface ActionContext extends Context {
route: Readonly<RouteInfo>;
parameters: any[];
}
export declare type KoaMiddleware = (ctx: Context, next: () => Promise<void>) => Promise<any>;
export interface MiddlewareDecorator {
name: "Middleware";
value: (string | symbol | MiddlewareFunction | Middleware)[];
target: "Controller" | "Action";
}
export interface Invocation<T = Context> {
ctx: Readonly<T>;
metadata?: T extends ActionContext ? Metadata : GlobalMetadata;
proceed(): Promise<ActionResult>;
}
export interface ActionInvocation extends Invocation<ActionContext> {
metadata: Metadata;
}
export declare type MiddlewareFunction<T = Context> = (invocation: T extends ActionContext ? Readonly<ActionInvocation> : Readonly<Invocation>) => Promise<ActionResult>;
export interface Middleware<T = Context> {
execute(invocation: Readonly<Invocation<T>>): Promise<ActionResult>;
}
export declare type CustomMiddleware = Middleware;
export declare type CustomMiddlewareFunction = MiddlewareFunction;
export declare type MiddlewareType = string | symbol | MiddlewareFunction | Middleware;
export interface MiddlewareMeta<T = MiddlewareType> {
middleware: T;
target?: "Controller" | "Action";
}
export declare namespace MiddlewareUtil {
function fromKoa(middleware: KoaMiddleware): Middleware;
function extractDecorators(route: RouteInfo): MiddlewareMeta<MiddlewareType>[];
}
export interface DependencyResolver {
resolve(type: Class | string | symbol): any;
}
export declare class DefaultDependencyResolver implements DependencyResolver {
private readonly registry;
register(id: string | symbol): ClassDecorator;
resolve(type: Class | string | symbol): any;
}
export interface Application {
/**
* Use plumier middleware registered from the registry
```
use("myMiddleware")
```
*/
use(middleware: string | symbol, scope?: "Global" | "Action"): Application;
/**
* Use plumier middleware
```
use(new MyMiddleware())
```
*/
use(middleware: Middleware, scope?: "Global" | "Action"): Application;
/**
* Use plumier middleware
```
use(x => x.proceed())
use(async x => {
return new ActionResult({ json: "body" }, 200)
})
```
*/
use(middleware: MiddlewareFunction, scope?: "Global" | "Action"): Application;
/**
* Set facility (advanced configuration)
```
set(new WebApiFacility())
```
*/
set(facility: Facility): Application;
/**
* Set part of configuration
```
set({ controllerPath: "./my-controller" })
```
* Can be specified more than one configuration
```
set({ mode: "production", rootPath: __dirname })
```
*/
set(config: Partial<Configuration>): Application;
/**
* Initialize Plumier app and return Koa application
```
app.initialize().then(koa => koa.listen(8000))
```
* For testing purposes
```
const koa = await app.initialize()
supertest(koa.callback())
```
*/
initialize(): Promise<Koa>;
/**
* Initialize Plumier and listen immediately to specific port.
*/
listen(port?: number | string): Promise<Server>;
}
export interface PlumierApplication extends Application {
readonly koa: Koa;
readonly config: Readonly<PlumierConfiguration>;
}
export declare class FormFile {
/**
* Size of the file (bytes)
*/
size: number;
/**
* Temporary path of the uploaded file
*/
path: string;
/**
* Original file name provided by client
*/
name: string;
/**
* Mime type of the file
*/
type: string;
/**
* The file timestamp
*/
mtime?: string | undefined;
constructor(
/**
* Size of the file (bytes)
*/
size: number,
/**
* Temporary path of the uploaded file
*/
path: string,
/**
* Original file name provided by client
*/
name: string,
/**
* Mime type of the file
*/
type: string,
/**
* The file timestamp
*/
mtime?: string | undefined);
/**
* Copy uploaded file into target directory, file name automatically generated
* @param dir target directory
* @returns the full path of the new location { fullPath, name }
*/
copy(dir: string): Promise<{
fullPath: string;
name: string;
}>;
}
export declare type FilterQueryType = "equal" | "partial" | "range" | "gte" | "gt" | "lte" | "lt" | "ne";
export interface NestedGenericControllerDecorator {
kind: "plumier-meta:relation-prop-name";
type: Class;
relation: string;
}
export declare type GenericControllers = [Class<ControllerGeneric>, Class<NestedControllerGeneric>];
export interface SelectQuery {
includeId?: true;
columns?: any;
relations?: any;
}
export interface Repository<T> {
find(offset: number, limit: number, query: any, select: SelectQuery, order: any): Promise<T[]>;
insert(data: Partial<T>): Promise<T>;
findById(id: any, select: SelectQuery): Promise<T | undefined>;
update(id: any, data: Partial<T>): Promise<T | undefined>;
delete(id: any): Promise<T | undefined>;
count(query?: any): Promise<number>;
}
export interface NestedRepository<P, T> {
find(pid: any, offset: number, limit: number, query: any, select: SelectQuery, order: any): Promise<T[]>;
insert(pid: any, data: Partial<T>): Promise<T>;
findParentById(id: any): Promise<P | undefined>;
findById(id: any, select: SelectQuery): Promise<T | undefined>;
update(id: any, data: Partial<T>): Promise<T | undefined>;
delete(id: any): Promise<T | undefined>;
count(pid: any, query?: any): Promise<number>;
}
export declare abstract class ControllerGeneric<T = any, TID = any> {
abstract readonly entityType: Class<T>;
}
export declare abstract class NestedControllerGeneric<P = any, T = any, PID = any, TID = any> {
abstract readonly entityType: Class<T>;
abstract readonly parentEntityType: Class<P>;
}
export declare type AccessModifier = "read" | "write" | "route";
export interface AuthorizationContext {
/**
* Current property value, only available on authorize read/write
*/
value?: any;
/**
* Current property's parent value, only available on authorize read/write
*/
parentValue?: any;
/**
* Current login user JWT claim
*/
user: JwtClaims | undefined;
/**
* Current request context
*/
ctx: ActionContext;
/**
* Metadata information of the current request
*/
metadata: Metadata;
/**
* Type of authorization applied read/write/route/filter
*/
access: AccessModifier;
}
export interface Authorizer {
authorize(info: AuthorizationContext): boolean | Promise<boolean>;
}
export interface AuthPolicy {
name: string;
equals(id: string, ctx: AuthorizationContext): boolean;
authorize(ctx: AuthorizationContext): Promise<boolean>;
conflict(other: AuthPolicy): boolean;
friendlyName(): string;
}
export declare type CustomConverter = (next: VisitorInvocation, ctx: ActionContext) => Result;
export interface Configuration {
mode: "debug" | "production";
/**
* List of registered global middlewares
*/
middlewares: {
middleware: (string | symbol | MiddlewareFunction | Middleware);
scope: "Global" | "Action";
}[];
/**
* Specify controller path (absolute or relative to entry point) or the controller classes array.
*/
controller: string | string[] | Class[] | Class;
/**
* Set custom dependency resolver for dependency injection
*/
dependencyResolver: DependencyResolver;
/**
* Define default response status for method type get/post/put/delete, default 200
```
responseStatus: { post: 201, put: 204, delete: 204 }
```
*/
responseStatus?: Partial<{
[key in HttpMethod]: number;
}>;
/**
* Set type converter visitor provided by typedconverter
*/
typeConverterVisitors: CustomConverter[];
/**
* Set custom route analyser functions
*/
analyzers?: RouteAnalyzerFunction[];
/**
* Global authorizations
*/
globalAuthorizations: string | string[];
/**
* Enable/disable authorization, when enabled all routes will be private by default. Default false
*/
enableAuthorization: boolean;
/**
* Root directory of the application, usually __dirname
*/
rootDir: string;
/**
* Trust proxy headers such as X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host and use its value
* to appropriate request properties: ip, protocol, host
*/
trustProxyHeader: boolean;
/**
* Implementation of generic controllers, first tuple for simple controller, second tuple for one to many controller
*/
genericController?: GenericControllers;
/**
* Generic controller name conversion to make plural route
*/
genericControllerNameConversion?: (x: string) => string;
/**
* Custom authorization policy
*/
authPolicies: Class<AuthPolicy>[];
/**
* Transform property value of response before its being parsed by response authorization
*/
responseTransformer?: (prop: PropertyReflection, value: any) => any;
/**
* Provide Open API security scheme https://swagger.io/docs/specification/authentication/
*/
openApiSecuritySchemes?: any;
}
export interface PlumierConfiguration extends Configuration {
facilities: Facility[];
}
export declare class HttpStatusError extends Error {
status: HttpStatus;
constructor(status: HttpStatus, message?: string);
}
export declare class ValidationError extends HttpStatusError {
issues: {
path: string[];
messages: string[];
}[];
constructor(issues: {
path: string[];
messages: string[];
}[]);
}
export declare type CurrentMetadataType = (PropertyReflection | ParameterReflection | MethodReflection | ClassReflection) & {
parent?: Class;
};
export interface Metadata {
/**
* Controller object graph
*/
controller: ClassReflection;
/**
* Current action object graph
*/
action: MethodReflection;
access?: string;
/**
* Action parameter helper, used to query current action parameter name or value
*/
actionParams: ParameterMetadata;
/**
* Reflection information about the current location (class/method/property) on which the decorator applied
*/
current?: CurrentMetadataType;
}
export declare type GlobalMetadata = Omit<Metadata, "actionParams">;
export declare class ParameterMetadata {
private parameters;
private meta;
constructor(parameters: any[], meta: ParameterReflection[]);
/**
* Get action parameter value by index
* @param index index of parameter
*/
get<T = any>(index: number): T | undefined;
/**
* Get action parameter value by parameter name (case insensitive)
*/
get<T = any>(name: string): T | undefined;
/**
* Get all parameter values
*/
values(): any[];
/**
* Get all action's parameter names
*/
names(): string[];
/**
* Check if action has specified parameter (case insensitive)
* @param name name of parameter
*/
hasName(name: string): boolean;
}
export declare class MetadataImpl implements Metadata {
/**
* Controller metadata object graph
*/
controller: ClassReflection;
/**
* Action metadata object graph
*/
action: MethodReflection;
/**
* Current action authorization access visible on route analysis, for example Public, Authenticated, Admin, User etc
*/
access?: string;
/**
* Action's parameters metadata, contains access to parameter values, parameter names etc.
* This property not available on Custom Parameter Binder and Global Middleware
*/
actionParams: ParameterMetadata;
/**
* Metadata information where target (Validator/Authorizer/Middleware) applied, can be a Property, Parameter, Method, Class.
*/
current?: CurrentMetadataType;
constructor(params: any[], routeInfo: RouteInfo, current?: CurrentMetadataType);
}
export declare namespace errorMessage {
const RouteDoesNotHaveBackingParam = "Route parameters ({0}) doesn't have appropriate backing parameter";
const DuplicateRouteFound = "Duplicate route found in {0}";
const ControllerPathNotFound = "Controller file or directory {0} not found";
const ObjectNotFound = "Object with id {0} not found in Object registry";
const ActionParameterDoesNotHaveTypeInfo = "Parameter binding skipped because action parameters doesn't have type information in ({0})";
const ModelWithoutTypeInformation = "Parameter binding skipped because {0} doesn't have type information on its properties";
const ArrayWithoutTypeInformation = "Parameter binding skipped because array element doesn't have type information in ({0})";
const PropertyWithoutTypeInformation = "Parameter binding skipped because property doesn't have type information in ({0})";
const GenericControllerImplementationNotFound = "Generic controller implementation not installed";
const GenericControllerRequired = "@genericController() required generic controller implementation, please install the appropriate facility";
const GenericControllerMissingTypeInfo = "{0} marked with @genericController() but doesn't have type information";
const GenericControllerInNonArrayProperty = "Nested generic controller can not be created using non array relation on: {0}.{1}";
const CustomRouteEndWithParameter = "Custom route path '{0}' on {1} entity, require path that ends with route parameter, example: animals/:animalId";
const CustomRouteRequiredTwoParameters = "Nested custom route path '{0}' on {1} entity, must have two route parameters, example: users/:userId/animals/:animalId";
const CustomRouteMustHaveOneParameter = "Custom route path '{0}' on {1} entity, must have one route parameter, example: animals/:animalId";
const EntityRequireID = "Entity {0} used by generic controller doesn't have an ID property";
const UnableToGetMemberDataType = "Unable to get data type of member {0}.{1}. Make sure to provide type information, or manage if its has cross reference to other class";
const UnableToInstantiateModel = "Unable to instantiate {0}. Domain model should not throw error inside constructor";
const UnableToConvertValue = "Unable to convert \"{0}\" into {1}";
const FileSizeExceeded = "File {0} size exceeded the maximum size";
const NumberOfFilesExceeded = "Number of files exceeded the maximum allowed";
}