nocobase/packages/resourcer/src/action.ts

324 lines
7.1 KiB
TypeScript
Raw Normal View History

import _ from 'lodash';
2020-10-24 07:34:43 +00:00
import compose from 'koa-compose';
import Resource from './resource';
import { requireModule } from './utils';
2020-10-24 07:34:43 +00:00
import { HandlerType } from './resourcer';
import Middleware, { MiddlewareType } from './middleware';
import { ActionParameterTypes, ActionParameter, UnknownParameter, PageParameter } from './parameter';
2020-10-24 07:34:43 +00:00
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 {
/**
*
*/
values?: any;
2020-10-24 07:34:43 +00:00
/**
*
*
*
* ['col1', 'col2', 'relation.col1'];
*
*
* {
* only: ['col1'],
* }
*
*
* {
* except: ['col1'],
* }
*/
fields?: FieldsOptions;
/**
*
*/
filter?: FilterOptions;
/**
*
*/
sort?: string | string[];
/**
*
*/
page?: number;
/**
*
*/
perPage?: number;
/**
*
*/
maxPerPage?: number;
2020-10-24 07:34:43 +00: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;
/**
*
*/
resourceIndex?: string;
2020-10-24 07:34:43 +00:00
/**
*
*/
associatedName?: string;
/**
*
*/
associatedIndex?: string;
2020-10-24 07:34:43 +00:00
/**
*
*/
2021-03-28 05:34:51 +00:00
associated?: any;
2020-10-24 07:34:43 +00:00
/**
*
*/
actionName?: string;
/**
*
*/
[key: string]: any;
}
export class Action {
protected handler: any;
protected resource: Resource;
protected name: ActionName;
protected options: ActionOptions;
protected parameters: Map<string, ActionParameter | UnknownParameter> = new Map();
2020-10-24 07:34:43 +00:00
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,
...params
} = options;
2020-10-24 07:34:43 +00:00
this.middlewares = Middleware.toInstanceArray(middleware || middlewares);
this.handler = handler;
this.options = options;
this.mergeParams(params, {});
2020-10-24 07:34:43 +00:00
}
get params(): ActionParams {
const result = this.parameters.get('_').get();
for (const paramType of this.parameters.values()) {
Object.assign(result, paramType.get());
}
return result;
2020-10-24 07:34:43 +00:00
}
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;
}
async mergeParams(params, strategies = {}) {
const typeKeys = new Set<string>();
Object.keys(params).forEach(key => {
for (const typeKey of ActionParameterTypes.getAllKeys()) {
if (typeof params[key] !== 'undefined' && key in ActionParameterTypes.get(typeKey).picking) {
typeKeys.add(typeKey);
}
2020-10-24 07:34:43 +00:00
}
});
let type;
for (const key of typeKeys) {
const strategy = strategies[key];
type = this.parameters.get(key);
if (!type) {
const Type = ActionParameterTypes.get(key);
if (!Type) {
throw new Error(`parameter type ${key} is unregistered`);
}
// @ts-ignore
type = new Type(params);
this.parameters.set(key, type);
}
type.merge(params, strategy);
2020-10-24 07:34:43 +00:00
}
type = this.parameters.get('_');
if (!type) {
type = new UnknownParameter(params);
this.parameters.set('_', type);
2020-10-24 07:34:43 +00:00
}
type.merge(params, strategies['_']);
2020-10-24 07:34:43 +00:00
}
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() {
2021-03-28 05:34:51 +00:00
return [...this.resource.resourcer.getMiddlewares(), ...this.getMiddlewareHandlers(), this.getHandler()].filter(Boolean);
2020-10-24 07:34:43 +00:00
}
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;