type TaskPayload = { timestamp: Date; lastTimestamp?: Date; timezone: string; }; type TaskContext = { retriesLeft: number; }; type TriggerArgs = { id: string; events: string[]; maxDuration?: number; // saniye retries?: number; run: ( payload: TaskPayload, tools: { ctx: TaskContext } ) => Promise | void; }; export class Hooks { private lastRunTimestamps: Record = {}; private triggers: TriggerArgs[] = []; public trigger(args: TriggerArgs) { this.triggers.push(args); } public async run(hookId: string) { const now = new Date(); const lastTimestamp = this.lastRunTimestamps[hookId]; const payload: TaskPayload = { timestamp: now, lastTimestamp, timezone: "UTC", }; this.lastRunTimestamps[hookId] = now; const context: TaskContext = { retriesLeft: this.triggers.find(hook => hook.id === hookId)?.retries ?? 0, }; await this.runWithRetry(this.triggers.find(hook => hook.id === hookId)!, payload, context); } private async runWithRetry( trigger: TriggerArgs, payload: TaskPayload, ctx: TaskContext ) { const start = Date.now(); try { await Promise.race([ trigger.run(payload, { ctx }), timeout(trigger.maxDuration ?? 60), ]); log("success", { hookId: trigger.id, time: new Date().toISOString() }); } catch (err) { //@ts-ignore log("error", { hookId: trigger.id, error: err }); if (ctx.retriesLeft > 0) { ctx.retriesLeft -= 1; //@ts-ignore log("warn", { hookId: trigger.id, msg: "Retrying..." }); await this.runWithRetry(trigger, payload, ctx); } } finally { const duration = Date.now() - start; //@ts-ignore log("info", { hookId: trigger.id, durationMs: duration }); } } public toJSON() { return this.triggers.map(trigger => ({ id: trigger.id, events: trigger.events, maxDuration: trigger.maxDuration, retries: trigger.retries, })); } } function log(arg0: string, arg1: { hookId: string; time: string; }) { // console.log(arg0, arg1); } function timeout(seconds: number) { return new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout reached")), seconds * 1000) ); } export const hooks = new Hooks();