tachybase_todo/packages/resourcer/src/action.ts

360 lines
8.0 KiB
TypeScript
Raw Normal View History

2020-10-24 15:34:43 +08:00
import _, { isArray } from 'lodash';
import compose from 'koa-compose';
import Resource from './resource';
import { requireModule, mergeFields } from './utils';
import { HandlerType } from './resourcer';
import Middleware, { MiddlewareType } from './middleware';
export type ActionType = string | HandlerType | ActionOptions;
export type DefaultActionType = 'list' | 'create' | 'get' | 'update' | 'destroy' | 'set' | 'add' | 'remove';
export type ActionName = DefaultActionType | Omit<String, DefaultActionType>;
export interface ActionContext {
action?: Action;
[key: string]: any;
}
export type FieldsOptions = string[] | {
only?: string[];
appends?: string[];
} | {
except?: string[];
appends?: string[];
};
export type FieldsOptionsFn = (ctx: ActionContext) => FieldsOptions | Promise<FieldsOptions>;
/**
*
*
* TODO
*/
export interface FilterOptions {
[key: string]: any;
}
export type FilterOptionsFn = (ctx: ActionContext) => FilterOptions | Promise<FieldsOptions>;
export type ParamsCallback = (ctx: ActionContext) => ActionParams | Promise<ActionParams>;
export interface ActionOptions {
/**
*
*/
defaultValues?: any;
/**
*
*
*
* ['col1', 'col2', 'relation.col1'];
*
*
* {
* only: ['col1'],
* }
*
*
* {
* except: ['col1'],
* }
*/
fields?: FieldsOptions;
/**
*
*/
filter?: FilterOptions;
/**
*
*/
sort?: string | string[];
/**
*
*/
page?: number;
/**
*
*/
perPage?: number;
/**
*
*/
maxPerPage?: number;
2020-10-24 15:34:43 +08:00
/**
*
*/
middleware?: MiddlewareType;
/**
*
*
* middleware
*/
middlewares?: MiddlewareType;
/**
* Action
*
* Function require
*/
handler?: HandlerType;
/**
*
*/
[key: string]: any;
}
/**
* action params action options
* - options
* - params +
*/
export interface ActionParams {
/**
*
*
* ActionOptions fields object onlyexceptappends
*/
fields?: {
only?: string[];
except?: string[];
appends?: string[];
};
/**
*
*/
filter?: FilterOptions;
/**
*
*
* ActionOptions sort array
*/
sort?: string[];
/**
*
*/
page?: number;
/**
*
*/
perPage?: number;
/**
* options.defaultValues + request.body
*/
values?: any;
/**
* Model
*/
resourceName?: string;
/**
*
*/
resourceKey?: string;
/**
*
*/
associatedName?: string;
/**
*
*/
associatedKey?: string;
/**
*
*/
associated ?: any;
/**
*
*/
actionName?: string;
/**
*
*/
[key: string]: any;
}
export const DEFAULT_PAGE = 1;
export const DEFAULT_PER_PAGE = 20;
export const MAX_PER_PAGE = 100;
2020-10-24 15:34:43 +08:00
export class Action {
protected handler: any;
protected resource: Resource;
protected name: ActionName;
protected options: ActionOptions;
protected parameters: ActionParams = {};
protected context: ActionContext = {};
public readonly middlewares: Array<Middleware> = [];
constructor(options: ActionOptions) {
options = requireModule(options);
if (typeof options === 'function') {
options = { handler: options };
}
const { middleware, middlewares = [], handler } = options;
this.middlewares = Middleware.toInstanceArray(middleware || middlewares);
this.handler = handler;
this.options = options;
}
get params(): ActionParams {
return this.parameters;
}
clone() {
const options = _.cloneDeep(this.options);
delete options.middleware;
delete options.middlewares;
const action = new Action(options);
action.setName(this.name);
action.setResource(this.resource);
action.middlewares.push(...this.middlewares);
return action;
}
setContext(context: any) {
this.context = context;
}
setParam(key: string, value: any) {
if (/\[\]$/.test(key)) {
key = key.substr(0, key.length - 2);
2020-10-24 15:34:43 +08:00
let values = _.get(this.parameters, key);
if (_.isArray(values)) {
values.push(value);
} else {
values = [];
values.push(value);
}
value = values;
}
_.set(this.parameters, key, value);
}
async mergeParams(params: ActionParams) {
const {
filter,
fields,
values,
page: paramPage,
perPage: paramPerPage,
per_page,
...restPrams
} = params;
const {
filter: optionsFilter,
fields: optionsFields,
page = DEFAULT_PAGE,
perPage = DEFAULT_PER_PAGE,
maxPerPage = MAX_PER_PAGE
} = this.options;
2020-10-24 15:34:43 +08:00
const options = _.omit(this.options, [
'defaultValues',
'filter',
'fields',
'maxPerPage',
'page',
'perPage',
'handler',
'middlewares',
'middleware',
2020-10-24 15:34:43 +08:00
]);
const data: ActionParams = {
...options,
...restPrams,
};
if (!_.isEmpty(this.options.defaultValues) || !_.isEmpty(values)) {
data.values = _.merge(_.cloneDeep(this.options.defaultValues), values);
}
// TODO: to be unified by style funciton
if (per_page || paramPerPage) {
data.perPage = per_page || paramPerPage;
}
if (paramPage || data.perPage) {
data.page = paramPage || page;
data.perPage = data.perPage == -1 ? maxPerPage : Math.min(data.perPage || perPage, maxPerPage);
2020-10-24 15:34:43 +08:00
}
// if (typeof optionsFilter === 'function') {
// this.parameters = _.cloneDeep(data);
// optionsFilter = await optionsFilter(this.context);
// }
if (!_.isEmpty(optionsFilter) || !_.isEmpty(filter)) {
data.filter = _.merge(filter, _.cloneDeep(optionsFilter));
}
// if (typeof optionsFields === 'function') {
// this.parameters = _.cloneDeep(data);
// optionsFields = await optionsFields(this.context);
// }
if (!_.isEmpty(optionsFields) || !_.isEmpty(fields)) {
data.fields = mergeFields(optionsFields, fields);
}
this.parameters = _.cloneDeep(data);
}
setResource(resource: Resource) {
this.resource = resource;
return this;
}
getResource() {
return this.resource;
}
getOptions(): ActionOptions {
return this.options;
}
setName(name: ActionName) {
this.name = name;
return this;
}
getName() {
return this.name;
}
getMiddlewareHandlers() {
return this.middlewares
.filter(middleware => middleware.canAccess(this.name))
.map(middleware => middleware.getHandler());
}
getHandler() {
const handler = requireModule(this.handler || this.resource.resourcer.getRegisteredHandler(this.name));
if (typeof handler !== 'function') {
throw new Error('Handler must be a function!');
}
return handler;
}
getHandlers() {
return [...this.resource.resourcer.getMiddlewares(),...this.getMiddlewareHandlers(), this.getHandler()].filter(Boolean);
}
async execute(context: any, next?: any) {
return await compose(this.getHandlers())(context, next);
}
static toInstanceMap(actions: object, resource?: Resource) {
return new Map(Object.entries(actions).map(([key, options]) => {
let action: Action;
if (options instanceof Action) {
action = options;
} else {
action = new Action(options);
}
action.setName(key);
action.setResource(resource);
resource && action.middlewares.unshift(...resource.middlewares);
return [key, action];
}));
}
}
export default Action;