nocobase/packages/resourcer/src/action.ts

324 lines
7.3 KiB
TypeScript
Raw Normal View History

2020-10-24 07:34:43 +00: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;
/**
*
*/
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 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);
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, ...restPrams } = params;
let { filter: optionsFilter, fields: optionsFields } = this.options;
const options = _.omit(this.options, [
'defaultValues', 'filter', 'fields', 'handler', 'middlewares', 'middleware',
]);
const data: ActionParams = {
...options,
...restPrams,
};
if (!_.isEmpty(this.options.defaultValues) || !_.isEmpty(values)) {
data.values = _.merge(_.cloneDeep(this.options.defaultValues), values);
}
if (data.per_page) {
data.perPage = data.per_page;
}
// 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;