feat(core): Add step-based progress tracking, task metadata and enhanced TaskManager scheduling/metadata APIs

This commit is contained in:
2025-09-06 13:36:04 +00:00
parent 6c9b975029
commit 9784a5eacf
12 changed files with 1191 additions and 140 deletions

View File

@@ -4,6 +4,7 @@ import {
AbstractDistributedCoordinator,
type IDistributedTaskRequestResult,
} from './taskbuffer.classes.distributedcoordinator.js';
import type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo } from './taskbuffer.interfaces.js';
export interface ICronJob {
cronString: string;
@@ -17,7 +18,7 @@ export interface ITaskManagerConstructorOptions {
export class TaskManager {
public randomId = plugins.smartunique.shortId();
public taskMap = new plugins.lik.ObjectMap<Task>();
public taskMap = new plugins.lik.ObjectMap<Task<any, any>>();
private cronJobManager = new plugins.smarttime.CronManager();
public options: ITaskManagerConstructorOptions = {
distributedCoordinator: null,
@@ -27,18 +28,18 @@ export class TaskManager {
this.options = Object.assign(this.options, options);
}
public getTaskByName(taskName: string): Task {
public getTaskByName(taskName: string): Task<any, any> {
return this.taskMap.findSync((task) => task.name === taskName);
}
public addTask(task: Task): void {
public addTask(task: Task<any, any>): void {
if (!task.name) {
throw new Error('Task must have a name to be added to taskManager');
}
this.taskMap.add(task);
}
public addAndScheduleTask(task: Task, cronString: string) {
public addAndScheduleTask(task: Task<any, any>, cronString: string) {
this.addTask(task);
this.scheduleTaskByName(task.name, cronString);
}
@@ -51,7 +52,7 @@ export class TaskManager {
return taskToTrigger.trigger();
}
public async triggerTask(task: Task) {
public async triggerTask(task: Task<any, any>) {
return task.trigger();
}
@@ -63,7 +64,7 @@ export class TaskManager {
this.handleTaskScheduling(taskToSchedule, cronString);
}
private handleTaskScheduling(task: Task, cronString: string) {
private handleTaskScheduling(task: Task<any, any>, cronString: string) {
const cronJob = this.cronJobManager.addCronjob(
cronString,
async (triggerTime: number) => {
@@ -86,7 +87,7 @@ export class TaskManager {
task.cronJob = cronJob;
}
private logTaskState(task: Task) {
private logTaskState(task: Task<any, any>) {
console.log(`Taskbuffer schedule triggered task >>${task.name}<<`);
const bufferState = task.buffered
? `buffered with max ${task.bufferMax} buffered calls`
@@ -95,7 +96,7 @@ export class TaskManager {
}
private async performDistributedConsultation(
task: Task,
task: Task<any, any>,
triggerTime: number,
): Promise<IDistributedTaskRequestResult> {
console.log('Found a distributed coordinator, performing consultation.');
@@ -123,7 +124,7 @@ export class TaskManager {
}
}
public async descheduleTask(task: Task) {
public async descheduleTask(task: Task<any, any>) {
await this.descheduleTaskByName(task.name);
}
@@ -145,4 +146,123 @@ export class TaskManager {
await this.options.distributedCoordinator.stop();
}
}
// Get metadata for a specific task
public getTaskMetadata(taskName: string): ITaskMetadata | null {
const task = this.getTaskByName(taskName);
if (!task) return null;
return task.getMetadata();
}
// Get metadata for all tasks
public getAllTasksMetadata(): ITaskMetadata[] {
return this.taskMap.getArray().map(task => task.getMetadata());
}
// Get scheduled tasks with their schedules and next run times
public getScheduledTasks(): IScheduledTaskInfo[] {
const scheduledTasks: IScheduledTaskInfo[] = [];
for (const task of this.taskMap.getArray()) {
if (task.cronJob) {
scheduledTasks.push({
name: task.name || 'unnamed',
schedule: task.cronJob.cronExpression,
nextRun: new Date(task.cronJob.getNextExecutionTime()),
lastRun: task.lastRun,
steps: task.getStepsMetadata?.(),
metadata: task.getMetadata(),
});
}
}
return scheduledTasks;
}
// Get next scheduled runs across all tasks
public getNextScheduledRuns(limit: number = 10): Array<{ taskName: string; nextRun: Date; schedule: string }> {
const scheduledRuns = this.getScheduledTasks()
.map(task => ({
taskName: task.name,
nextRun: task.nextRun,
schedule: task.schedule,
}))
.sort((a, b) => a.nextRun.getTime() - b.nextRun.getTime())
.slice(0, limit);
return scheduledRuns;
}
// Add, execute, and remove a task while collecting metadata
public async addExecuteRemoveTask<T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }>>(
task: Task<T, TSteps>,
options?: {
schedule?: string;
trackProgress?: boolean;
}
): Promise<ITaskExecutionReport> {
// Add task to manager
this.addTask(task);
// Optionally schedule it
if (options?.schedule) {
this.scheduleTaskByName(task.name!, options.schedule);
}
const startTime = Date.now();
const progressUpdates: Array<{ stepName: string; timestamp: number }> = [];
try {
// Execute the task
const result = await task.trigger();
// Collect execution report
const report: ITaskExecutionReport = {
taskName: task.name || 'unnamed',
startTime,
endTime: Date.now(),
duration: Date.now() - startTime,
steps: task.getStepsMetadata(),
stepsCompleted: task.getStepsMetadata()
.filter(step => step.status === 'completed')
.map(step => step.name),
progress: task.getProgress(),
result,
};
// Remove task from manager
this.taskMap.remove(task);
// Deschedule if it was scheduled
if (options?.schedule && task.name) {
this.descheduleTaskByName(task.name);
}
return report;
} catch (error) {
// Create error report
const errorReport: ITaskExecutionReport = {
taskName: task.name || 'unnamed',
startTime,
endTime: Date.now(),
duration: Date.now() - startTime,
steps: task.getStepsMetadata(),
stepsCompleted: task.getStepsMetadata()
.filter(step => step.status === 'completed')
.map(step => step.name),
progress: task.getProgress(),
error: error as Error,
};
// Remove task from manager even on error
this.taskMap.remove(task);
// Deschedule if it was scheduled
if (options?.schedule && task.name) {
this.descheduleTaskByName(task.name);
}
throw errorReport;
}
}
}