UNPKG

@plumier/core

Version:

Delightful Node.js Rest Framework

523 lines (522 loc) • 18.4 kB
/// <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"; }