kite-framework
Version:
Modern, fast, flexible HTTP-JSON-RPC framework
276 lines (275 loc) • 9.91 kB
TypeScript
/***
* Copyright (c) 2017 [Arthur Xie]
* <https://github.com/kite-js/kite>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
import 'reflect-metadata';
import { FilterRule } from './model';
/**
* Controller entry point definition structure
*/
export declare type InputRules = {
[name: string]: FilterRule;
};
/**
* Kite controller entry point decorator.
*
* ## Description
*
* Entry decorator marks a function of controller as "entry point" for Kite, the framework will invoke it
* when request comes in.
*
* The name of entry point function is not limited, you can name it at will.
* Please note that __only one entry point__ can be annotated in a Kite controller, if more
* than one `@Entry()` appeares in controller, an error will be given.
*
* ## How to use
* An entry point must be an
* "[asynchronous function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)",
* means it must be declared like following:
*
* ```typescript
* @Controller()
* class AController {
* @Entry()
* async exec() {
* // statments
* }
* }
* ```
* or with a return type declared:
* ```typescript
* @Controller()
* class AController {
* @Entry()
* exec(): Promise<any> {
* // statments
* }
* }
* ```
* or combine both:
* ```typescript
* @Controller()
* class AController {
* @Entry()
* async exec(): Promise<any> {
* // statments
* }
* }
* ```
*
* ## Parameter mapping
* The parameters of entry point function are mapped to client inputs or / and some other special context variables,
* base on types paramters:
* + __javascript types (number, string, boolean, array, boolean)__ - search for property which has the same name with the
* parameter in the raw input object, and parse input value to declared types
* + __Other objects__ - create a parameter object with "new XObject(inputs.paramName)" and set to these parameters,
* these objects should support constructor initialization, such as Date `new Date(inputs)` and MongoDB ObjectId `new ObjectId(inputs)`
* + __Kite model__ - create an model and filter the inputs with declared rules
* + __[IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)__ - current IncomingMessage (request) object
*
* ### Basic mappings
* A shortcut to map client inputs to controllers is define map rules for parameters of entry points.
* For example, let's assume "http://localhost:4000/UserUpdate?_id=1000&name=Tom" mapped to a controller named "UserUpdateController":
* ```typescript
* @Controller()
* export class UserUpdateController {
* @Entry()
* async exec(_id: number, name: string, country: string = 'unknown') {
* // do your things here
* console.log(_id, name, country);
* return {_id, name, country};
* }
* }
* ```
* the server console will output "
*
* `> 1000 Tom unknow`
*
* Kite maps "_id" and "name" from URL to parameters of controller entry point,
* both "_id" and "name" are required fields, if any of them is omitted, Kite will give an error to clients;
* the third parameter "country" is assigned with a default value, Kite will treat this field as an "optional" parameter,
* means process is continued with the default value even it's omitted from request.
* And, if "country" is given in request, parameter "country" of "UserUpdateController.exec()" is set to a given value,
* for example http://localhost:4000/UserUpdate?_id=1000&name=Tom&country=US" outputs:
*
* `> 1000 Tom US`
*
* Kite treats non-default arguments as "required" fields for entry points, and arguments with default values are treated
* as optional fields, so you can place the optional arguments any where:
* ```typescript
* export class UserUpdateController {
* @Entry()
* async exec(_id: number, country: string = 'unknown', name: string) {
* // do your things here
* console.log(_id, name, country);
* return {_id, name, country};
* }
* }
* ```
*
* Sometimes you might need arguments to be "optional" without default values, in this case, you should
* assign 'undefined' as default values to these arguments, the following code shows this trick:
* ```typescript
* export class UserUpdateController {
* @Entry()
* async exec(_id: number, country: string = undefined, name: string) {
* // do your things here
* console.log(_id, name, country);
* return {_id, name, country};
* }
* }
* ```
*
* This "required/optional" checking mechanism is different from Kite model, where fields be treated as
* "optional" if `required: true` is not explicitly annonced in "@In()".
*
* Kite allows you define rules for each parameter at `@Entry()` decorator as well, rule definition is as same as `@In()`:
* ```typescript
* @Controller()
* export class UserUpdateController {
* @Entry({
* name: { min: 3 } // "name" minimal length is 3
* })
* async exec(_id: number, name: string, country: string = 'unknown') {
* // do your things here
* }
* }
* ```
* Please note that, the above example has implicit "required" rules applied to "_id" and "name", even though there is no "required: true"
* defined in the rule.
*
* ### Kite model mapping
* Any argument annonced as type of Kite model will cause Kite to map entire raw input object to this argument, this is useful when coding
* "insert", "update" APIs, generally these APIs accept the data that maps to database tables or documents, it's not friendly writing
* losts of arguments in entry point functions, so "Kite model" is a workaround.
* ```typescript
* @Model()
* export class UserModel {
* _id: number;
*
* @In({required: true, min: 3}) // limit input "name" minimal length to 3
* name: string;
*
* @In({min: 10}) // limit input "age" minimal value to 10
* age: number;
*
* @In() // no rule for input "country", accept any value
* country: string;
* }
*
* @Controller()
* export class UserCreateController {
* @Entry()
* async exec(user: UserModel) {
* // db.user.insertOne(user); // insert to DB, that's it
* return { success: true };
* }
*
* }
* ```
* The above code shows a controller named "UserCreateController", accept `user: UserModel` as parameter, `UserModel` mapping
* client inputs: "name", "age" and "country" as input values, And, `UserModel` is also mapping to a database table -
* let's assume table name is "user" - which owns fields "_id", "name", "age", "country", in this table, "_id" is generated as
* key by database when data is inserted.
*
* Here, "_id" is excluded from client inputs because it's a "key" for this row of data and you don't want a client-input value
* set to this key.
*
* Before Kite call this controller, the framework checks the input data follow the rules that defined in "@In(...)" for you.
*
* With this feature, Kite is extremely easy to map & validate complex data objects:
* ```typescript
* @Model() export class Addr {
* @In()
* addr: string;
*
* @In()
* city: string;
*
* @In()
* state: string;
*
* @In()
* zipcode: string;
* }
*
* @Model() export class User {
*
* _id: number;
*
* @In()
* name: string;
*
* @In()
* addr: Addr;
* }
*
* @Controller()
* export class UserCreateController {
* @Entry()
* async exec(user: User) {
* // save 'user' to db
* }
* }
* ```
*
* ### Mixing basic types and Kite model
* As the above example shows, "_id" is not an input field, this is reasonable to insert data to database,
* but it's unreasonable to update, we certainly require "_id" here. Therefore, you can mix basic types
* and Kite model in an entry point:
* ```typescript
* export class UserUpdateController {
* @Entry()
* async exec(_id: number, user: User) {
* // update 'user' to db
* }
* }
* ```
*
*/
export declare function Entry(config?: InputRules): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
/**
* Test a class has entry point or not
* @param controller any value
*/
export declare function hasEntryPoint(controller: object): boolean;
/**
* Get parameter reflection object from a controller
* @param controller constroller instance
*/
export declare function getEntryParams(controller: object): any;
/**
* Tell Kite only map client input to a Kite model and its children,
* without calling the constructor.
*
* This decorator can be only applied to Kite controller entry point function,
* and must be placed after `@Entry()` decorator, for example:
* ```typescript
* import { Controller, Entry } from 'kite-framework';
*
* @Controller()
* export class TypesController {
* @Entry()
* @MapInputOnly
* async exec(str: string, num: number, bool: boolean, date: Date = undefined) {
* return { values: { str, num, bool, date } };
* }
* }
* ```
*/
export declare function MapInputOnly(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void;
/**
* Test a Kite model is only map client inputs or not
* @param target
*/
export declare function isMapInputOnly(target: Object): boolean;