@nelts/agent
Version:
agent for nelts
187 lines (170 loc) • 6.38 kB
text/typescript
import 'reflect-metadata';
import { WidgetComponent, Processer } from '@nelts/process';
import { Factory, InCommingMessage } from '@nelts/factory';
import { RequireDefault } from '@nelts/utils';
import { CronJob } from 'cron';
import { Agent as AgentMessager, MessageReceiveDataOptions } from '@nelts/messager';
import AgentPlugin from './plugin';
import AgentComponent, { AgentComponentImplements } from './components/base';
import DecoratorNameSpace from './decorators/namespace';
import { ScheduleDecoratorType } from './decorators/schedule';
import { runFunctionalWithPromise } from '@nelts/utils';
import AgentBootstrapCompiler from './compilers/bootstrap';
import Ipc from './decorators/ipc';
import Auto from './decorators/auto';
import Namespace from './decorators/namespace';
import Schedule from './decorators/schedule';
type AgentComponentConstructorType = { new(app: AgentFactory): AgentComponentImplements };
type IPC_POOL = {
[name: string]: boolean;
}
export default class AgentFactory extends Factory<AgentPlugin> implements WidgetComponent {
private _name: string;
private _agentComponentConstructor: AgentComponentConstructorType;
private _target: AgentComponentImplements;
private _messager: AgentMessager<this>;
private _ipc_pool: IPC_POOL = {};
private _jobs: { [job:string]: {
status: boolean,
job: CronJob,
} } = {};
constructor(processer: Processer, args: InCommingMessage) {
super(processer, args, AgentPlugin);
const target = RequireDefault<AgentComponentConstructorType>(args.file);
this._name = args.name;
this._agentComponentConstructor = target;
this._target = new target(this);
this._messager = new AgentMessager(this, this.inCommingMessage.mpid);
if (!(this._target instanceof AgentComponent)) throw new Error('agent component must instanceof AgentComponent');
this.on('health', (post: (data: any) => any, socket?: any) => this.convertHealth(post, socket));
this.on('ready', (socket?:any) => {
if (typeof this._target.ready === 'function') {
return this._target.ready(socket);
}
});
this.on('hybrid', async (message: MessageReceiveDataOptions, post: (data: any, reply: boolean) => any, socket?: any) => {
const method = message.method;
switch (method) {
case 'event:put:job': post(this.startHybridJob(message.data), true); break;
case 'event:delete:job': post(this.stopHybridJob(message.data), true); break;
default:
if (this._ipc_pool[method] !== undefined && typeof this._target[method] === 'function') {
const value = await this._target[method](message.data, socket);
post(value, this._ipc_pool[method]);
}
}
})
}
get messager() {
return this._messager;
}
private startHybridJob(property: string) {
const target = this._jobs[property];
if (!target) return false;
if (target.status) return true;
target.job.start();
this._jobs[property].status = true;
return true;
}
private stopHybridJob(property: string) {
const target = this._jobs[property];
if (!target) return false;
if (!target.status) return true;
target.job.stop();
this._jobs[property].status = false;
return true;
}
private async convertHealth(post: (data: any) => any, socket?: any) {
const result: {
status: boolean,
time: Date,
pid: number,
value?: any,
} = {
status: true,
time: new Date(),
pid: process.pid,
}
if (this._target.health) {
const value = await this._target.health(socket);
if (typeof value === 'object') return post(Object.assign(value, result));
result.value = value;
}
post(result);
}
async componentWillCreate() {
await super.componentWillCreate();
this.compiler.addCompiler(AgentBootstrapCompiler);
this._target.beforeCreate && await this._target.beforeCreate();
}
async componentDidCreated() {
await super.componentDidCreated();
this.resolveWithAgentDecorators();
this._target.created && await this._target.created();
await this.sync('ServerStarted');
}
async componentWillDestroy() {
this._target.beforeDestroy && await this._target.beforeDestroy();
await this.sync('ServerStopping');
}
async componentDidDestroyed() {
this._target.destroyed && await this._target.destroyed();
await this.sync('ServerStopped');
}
componentCatchError(err: Error) {
this.logger.error(err);
this._target.catchError && this._target.catchError(err);
}
componentReceiveMessage(message: MessageReceiveDataOptions, socket?: any) {
this.messager.receiveMessage(message, socket);
}
private resolveWithAgentDecorators() {
const targetProperties = Object.getOwnPropertyNames(this._agentComponentConstructor.prototype);
for (let i = 0; i < targetProperties.length; i++) {
const property = targetProperties[i];
const target = this._agentComponentConstructor.prototype[property];
if (property === 'constructor') continue;
this.createNewJob(property, {
cron: Reflect.getMetadata(DecoratorNameSpace.SCHEDULE, target),
auto: !!Reflect.getMetadata(DecoratorNameSpace.SCHEDULE_AUTO, target),
run: !!Reflect.getMetadata(DecoratorNameSpace.SCHEDULE_RUN, target),
});
if (Reflect.getMetadata(DecoratorNameSpace.IPC, target)) {
this._ipc_pool[property] = !!Reflect.getMetadata(DecoratorNameSpace.FEEDBACK, target);
}
}
}
private createNewJob(property: string, options: {
cron: ScheduleDecoratorType,
auto?: boolean,
run?: boolean,
}) {
if (this._jobs[property]) return;
if (!options.cron) return;
const job = new CronJob(options.cron, (...args: any[]) => {
if (this._target[property]) {
runFunctionalWithPromise(this._target[property](...args)).then(result => {
if (result === true) {
job.stop();
this._jobs[property].status = false;
}
}).catch(e => this.logger.error(e));
}
}, undefined, false, undefined, this._target, !!options.run);
options.auto && job.start();
this._jobs[property] = {
status: !!options.auto,
job
}
return job;
}
}
export {
Ipc,
Auto,
Namespace,
Schedule,
AgentPlugin,
AgentComponent,
AgentComponentImplements,
}