@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
165 lines (164 loc) • 6.76 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hook = void 0;
const errors_1 = require("../../modules/errors");
const collator_1 = require("../collator");
const pipe_1 = require("../pipe");
const task_1 = require("../task");
const telemetry_1 = require("../telemetry");
const stream_1 = require("../../types/stream");
const activity_1 = require("./activity");
/**
* Supports `signal hook`, `time hook`, and `cycle hook` patterns
*/
class Hook extends activity_1.Activity {
constructor(config, data, metadata, hook, engine, context) {
super(config, data, metadata, hook, engine, context);
}
//******** INITIAL ENTRY POINT (A) ********//
async process() {
this.logger.debug('hook-process', {
jid: this.context.metadata.jid,
gid: this.context.metadata.gid,
aid: this.metadata.aid,
});
let telemetry;
try {
await this.verifyEntry();
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
telemetry.startActivitySpan(this.leg);
if (this.doesHook()) {
//sleep and wait to awaken upon a signal
await this.doHook(telemetry);
}
else {
//end the activity and transition to its children
await this.doPassThrough(telemetry);
}
return this.context.metadata.aid;
}
catch (error) {
if (error instanceof errors_1.InactiveJobError) {
this.logger.error('hook-inactive-job-error', { error });
return;
}
else if (error instanceof errors_1.GenerationalError) {
this.logger.info('process-event-generational-job-error', { error });
return;
}
else if (error instanceof errors_1.GetStateError) {
this.logger.error('hook-get-state-error', { error });
return;
}
else if (error instanceof errors_1.CollationError) {
if (error.fault === 'duplicate') {
this.logger.info('hook-collation-overage', {
job_id: this.context.metadata.jid,
guid: this.context.metadata.guid,
});
return;
}
//unknown collation error
this.logger.error('hook-collation-error', { error });
}
else {
this.logger.error('hook-process-error', { error });
}
telemetry?.setActivityError(error.message);
throw error;
}
finally {
telemetry?.endActivitySpan();
this.logger.debug('hook-process-end', {
jid: this.context.metadata.jid,
gid: this.context.metadata.gid,
aid: this.metadata.aid,
});
}
}
/**
* does this activity use a time-hook or web-hook
*/
doesHook() {
if (this.config.sleep) {
const duration = pipe_1.Pipe.resolve(this.config.sleep, this.context);
return !isNaN(duration) && Number(duration) > 0;
}
return !!this.config.hook?.topic;
}
async doHook(telemetry) {
const transaction = this.store.transact();
await this.registerHook(transaction);
this.mapOutputData();
this.mapJobData();
await this.setState(transaction);
await collator_1.CollatorService.authorizeReentry(this, transaction);
await this.setStatus(0, transaction);
await transaction.exec();
telemetry.mapActivityAttributes();
}
async doPassThrough(telemetry) {
const transaction = this.store.transact();
let multiResponse;
this.adjacencyList = await this.filterAdjacent();
this.mapOutputData();
this.mapJobData();
await this.setState(transaction);
await collator_1.CollatorService.notarizeEarlyCompletion(this, transaction);
await this.setStatus(this.adjacencyList.length - 1, transaction);
multiResponse = (await transaction.exec());
telemetry.mapActivityAttributes();
const jobStatus = this.resolveStatus(multiResponse);
const attrs = { 'app.job.jss': jobStatus };
const messageIds = await this.transition(this.adjacencyList, jobStatus);
if (messageIds.length) {
attrs['app.activity.mids'] = messageIds.join(',');
}
telemetry.setActivityAttributes(attrs);
}
async getHookRule(topic) {
const rules = await this.store.getHookRules();
return rules?.[topic]?.[0];
}
async registerHook(transaction) {
if (this.config.hook?.topic) {
return await this.engine.taskService.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), this.context.metadata.expire, transaction);
}
else if (this.config.sleep) {
const duration = pipe_1.Pipe.resolve(this.config.sleep, this.context);
await this.engine.taskService.registerTimeHook(this.context.metadata.jid, this.context.metadata.gid, `${this.metadata.aid}${this.metadata.dad || ''}`, 'sleep', duration, this.metadata.dad || '');
return this.context.metadata.jid;
}
}
//******** SIGNAL RE-ENTRY POINT ********//
async processWebHookEvent(status = stream_1.StreamStatus.SUCCESS, code = 200) {
this.logger.debug('hook-process-web-hook-event', {
topic: this.config.hook.topic,
aid: this.metadata.aid,
status,
code,
});
const taskService = new task_1.TaskService(this.store, this.logger);
const data = { ...this.data };
const signal = await taskService.processWebHookSignal(this.config.hook.topic, data);
if (signal) {
const [jobId, _aid, dad, gId] = signal;
this.context.metadata.jid = jobId;
this.context.metadata.gid = gId;
this.context.metadata.dad = dad;
await this.processEvent(status, code, 'hook');
if (code === 200) {
await taskService.deleteWebHookSignal(this.config.hook.topic, data);
} //else => 202/keep alive
} //else => already resolved
}
async processTimeHookEvent(jobId) {
this.logger.debug('hook-process-time-hook-event', {
jid: jobId,
gid: this.context.metadata.gid,
aid: this.metadata.aid,
});
await this.processEvent(stream_1.StreamStatus.SUCCESS, 200, 'hook');
}
}
exports.Hook = Hook;