/// import Parser from '../parse/parser'; import DenaliObject from '../metal/object'; import Request from './request'; import { ServerResponse } from 'http'; import Logger from './logger'; import ConfigService from './config'; import { RelationshipConfigs } from '../render/serializer'; export interface Responder { (params: ResponderParams): any; } /** * The parser determines the exact shape and structure of the arguments object * passed into your Action's respond method. However, the common convention is * to at least expose the properties listed below. * * *Note for Typescript users:* * * It's possible to have a parser that returns a query object with non-string * properties (i.e. your parser automatically converts the `page=4` query param * into a number). In that case, you should probably define your own interface * that extends from this, and use that interface to type your respond method * argument. */ export interface ResponderParams { body?: any; query?: any; headers?: any; params?: any; [key: string]: any; } export interface RenderOptions { /** * The view class that should be used to render this response. Overrides the * `serializer` setting. This is useful if you want complete, low-level * control over the rendering process - you'll have direct access to the * response object, and can use it to render however you want. Render with a * streaming JSON renderer, use an HTML templating engine, a binary protocol, * etc. */ view?: string; /** * Explicitly set the name of the serializer that should be used to render * this response. If not provided, and the response body is a Model or array * of Models, it will try to find a matching serializer and use that. If it * can't find the matching serializer, or if the response body is another * kind of object, it will fall back to the application serializer. */ serializer?: string; /** * Override which attributes should be serialized. */ attributes?: string[]; /** * Override which relationships should be serialized. */ relationships?: RelationshipConfigs; [key: string]: any; } export interface Filter { (params: ResponderParams): boolean | void; } export declare type FilterSpecifier = string | Filter; /** * Actions form the core of interacting with a Denali application. They are the * controller layer in the MVC architecture, taking in incoming requests, * performing business logic, and handing off to the renderer to send the * response. * * When a request comes in, Denali will invoke the `respond` method on the * matching Action class. The return value (or resolved return value) of this * method is used to render the response. * * Actions also support filters. Simply define a method on your action, and add * the method name to the `before` or `after` array. Filters behave similar to * responders in that they receive the request params and can return a promise * which will be waited on before continuing. Filters are inheritable, so child * classes will run filters added by parent classes. * * @package runtime * @since 0.1.0 */ export default abstract class Action extends DenaliObject { /** * Invoked before the `respond()` method. The framework will invoke filters * from parent classes and mixins in the same order the mixins were applied. * * Filters can be synchronous, or return a promise (which will pause the * before/respond/after chain until it resolves). * * If a before filter returns any value (or returns a promise which resolves * to any value) other than null or undefined, Denali will attempt to render * that response and halt further processing of the request (including * remaining before filters). * * Filters must be defined as static properties to allow Denali to extract * the values. Instance fields are not visible until instantiation, so * there's no way to build an "accumulated" value from each step in the * inheritance chain. * * @since 0.1.0 */ static before: FilterSpecifier[]; /** * Invoked after the `respond()` method. The framework will invoke filters * from parent classes and mixins in the same order the mixins were applied. * * Filters can be synchronous, or return a promise (which will pause the * before/respond/after chain until it resolves). * * Filters must be defined as static properties to allow Denali to extract * the values. Instance fields are not visible until instantiation, so * there's no way to build an "accumulated" value from each step in the * inheritance chain. * * @since 0.1.0 */ static after: FilterSpecifier[]; /** * Application config * * @since 0.1.0 */ config: ConfigService; /** * Force which parser should be used for parsing the incoming request. * * By default it uses the application parser, but you can override with the * name of the parser you'd rather use instead. * * @since 0.1.0 */ parser: Parser; /** * Automatically inject the logger into all actions * * @since 0.1.0 */ logger: Logger; /** * The incoming Request that this Action is responding to. * * @since 0.1.0 */ request: Request; /** * The outgoing HTTP server response * * @since 0.1.0 */ response: ServerResponse; /** * Track whether or not we have rendered yet */ protected hasRendered: boolean; /** * The path to this action, i.e. 'users/create' */ actionPath: string; /** * Render the response body * * @since 0.1.0 */ render(body: any, options?: RenderOptions): Promise; render(status: number, body?: any, options?: RenderOptions): Promise; /** * Invokes the action. Determines the best responder method for content * negotiation, then executes the filter/responder chain in sequence, * handling errors and rendering the response. * * You shouldn't invoke this directly - Denali will automatically wire up * your routes to this method. * * @since 0.1.0 */ run(request: Request, response: ServerResponse): Promise; /** * The default responder method. You should override this method with * whatever logic is needed to respond to the incoming request. * * @since 0.1.0 */ abstract respond(params: ResponderParams): any; /** * Invokes the filters in the supplied chain in sequence. */ protected _invokeFilters(chain: Filter[], parsedRequest: ResponderParams): Promise; /** * Walk the prototype chain of this Action instance to find all the `before` * and `after` arrays to build the complete filter chains. * * Caches the result on the child Action class to avoid the potentially * expensive prototype walk on each request. * * Throws if it encounters the name of a filter method that doesn't exist. */ protected _buildFilterChains(): { beforeChain: Filter[]; afterChain: Filter[]; }; protected _buildFilterChain(stageName: 'before' | 'after', cache: Map, prototypes: typeof Action[]): void; }