feat(core): Add step-based progress tracking, task metadata and enhanced TaskManager scheduling/metadata APIs
This commit is contained in:
		@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@push.rocks/taskbuffer',
 | 
			
		||||
  version: '3.1.10',
 | 
			
		||||
  version: '3.2.0',
 | 
			
		||||
  description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								ts/index.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								ts/index.ts
									
									
									
									
									
								
							@@ -1,10 +1,18 @@
 | 
			
		||||
export { Task } from './taskbuffer.classes.task.js';
 | 
			
		||||
export type { ITaskFunction } from './taskbuffer.classes.task.js';
 | 
			
		||||
export type { ITaskFunction, StepNames } from './taskbuffer.classes.task.js';
 | 
			
		||||
export { Taskchain } from './taskbuffer.classes.taskchain.js';
 | 
			
		||||
export { Taskparallel } from './taskbuffer.classes.taskparallel.js';
 | 
			
		||||
export { TaskManager } from './taskbuffer.classes.taskmanager.js';
 | 
			
		||||
export { TaskOnce } from './taskbuffer.classes.taskonce.js';
 | 
			
		||||
export { TaskRunner } from './taskbuffer.classes.taskrunner.js';
 | 
			
		||||
export { TaskDebounced } from './taskbuffer.classes.taskdebounced.js';
 | 
			
		||||
 | 
			
		||||
// Task step system
 | 
			
		||||
export { TaskStep } from './taskbuffer.classes.taskstep.js';
 | 
			
		||||
export type { ITaskStep } from './taskbuffer.classes.taskstep.js';
 | 
			
		||||
 | 
			
		||||
// Metadata interfaces
 | 
			
		||||
export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo } from './taskbuffer.interfaces.js';
 | 
			
		||||
 | 
			
		||||
import * as distributedCoordination from './taskbuffer.classes.distributedcoordinator.js';
 | 
			
		||||
export { distributedCoordination };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import * as plugins from './taskbuffer.plugins.js';
 | 
			
		||||
import { BufferRunner } from './taskbuffer.classes.bufferrunner.js';
 | 
			
		||||
import { CycleCounter } from './taskbuffer.classes.cyclecounter.js';
 | 
			
		||||
import { TaskStep, type ITaskStep } from './taskbuffer.classes.taskstep.js';
 | 
			
		||||
import type { ITaskMetadata } from './taskbuffer.interfaces.js';
 | 
			
		||||
 | 
			
		||||
import { logger } from './taskbuffer.logging.js';
 | 
			
		||||
 | 
			
		||||
@@ -14,18 +16,21 @@ export interface ITaskSetupFunction<T = undefined> {
 | 
			
		||||
 | 
			
		||||
export type TPreOrAfterTaskFunction = () => Task<any>;
 | 
			
		||||
 | 
			
		||||
export class Task<T = undefined> {
 | 
			
		||||
  public static extractTask<T = undefined>(
 | 
			
		||||
    preOrAfterTaskArg: Task<T> | TPreOrAfterTaskFunction,
 | 
			
		||||
  ): Task<T> {
 | 
			
		||||
// Type helper to extract step names from array
 | 
			
		||||
export type StepNames<T> = T extends ReadonlyArray<{ name: infer N }> ? N : never;
 | 
			
		||||
 | 
			
		||||
export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []> {
 | 
			
		||||
  public static extractTask<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
 | 
			
		||||
    preOrAfterTaskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
 | 
			
		||||
  ): Task<T, TSteps> {
 | 
			
		||||
    switch (true) {
 | 
			
		||||
      case !preOrAfterTaskArg:
 | 
			
		||||
        return null;
 | 
			
		||||
      case preOrAfterTaskArg instanceof Task:
 | 
			
		||||
        return preOrAfterTaskArg as Task<T>;
 | 
			
		||||
        return preOrAfterTaskArg as Task<T, TSteps>;
 | 
			
		||||
      case typeof preOrAfterTaskArg === 'function':
 | 
			
		||||
        const taskFunction = preOrAfterTaskArg as TPreOrAfterTaskFunction;
 | 
			
		||||
        return taskFunction();
 | 
			
		||||
        return taskFunction() as unknown as Task<T, TSteps>;
 | 
			
		||||
      default:
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -45,9 +50,9 @@ export class Task<T = undefined> {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  public static isTaskTouched<T = undefined>(
 | 
			
		||||
    taskArg: Task<T> | TPreOrAfterTaskFunction,
 | 
			
		||||
    touchedTasksArray: Task<T>[],
 | 
			
		||||
  public static isTaskTouched<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
 | 
			
		||||
    taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
 | 
			
		||||
    touchedTasksArray: Task<T, TSteps>[],
 | 
			
		||||
  ): boolean {
 | 
			
		||||
    const taskToCheck = Task.extractTask(taskArg);
 | 
			
		||||
    let result = false;
 | 
			
		||||
@@ -59,9 +64,9 @@ export class Task<T = undefined> {
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static runTask = async <T>(
 | 
			
		||||
    taskArg: Task<T> | TPreOrAfterTaskFunction,
 | 
			
		||||
    optionsArg: { x?: any; touchedTasksArray?: Task<T>[] },
 | 
			
		||||
  public static runTask = async <T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
 | 
			
		||||
    taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
 | 
			
		||||
    optionsArg: { x?: any; touchedTasksArray?: Task<T, TSteps>[] },
 | 
			
		||||
  ) => {
 | 
			
		||||
    const taskToRun = Task.extractTask(taskArg);
 | 
			
		||||
    const done = plugins.smartpromise.defer();
 | 
			
		||||
@@ -80,9 +85,17 @@ export class Task<T = undefined> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    taskToRun.running = true;
 | 
			
		||||
    taskToRun.runCount++;
 | 
			
		||||
    taskToRun.lastRun = new Date();
 | 
			
		||||
    
 | 
			
		||||
    // Reset steps at the beginning of task execution
 | 
			
		||||
    taskToRun.resetSteps();
 | 
			
		||||
 | 
			
		||||
    done.promise.then(async () => {
 | 
			
		||||
      taskToRun.running = false;
 | 
			
		||||
      
 | 
			
		||||
      // Complete all steps when task finishes
 | 
			
		||||
      taskToRun.completeAllSteps();
 | 
			
		||||
 | 
			
		||||
      // When the task has finished running, resolve the finished promise
 | 
			
		||||
      taskToRun.resolveFinished();
 | 
			
		||||
@@ -98,7 +111,7 @@ export class Task<T = undefined> {
 | 
			
		||||
      ...optionsArg,
 | 
			
		||||
    };
 | 
			
		||||
    const x = options.x;
 | 
			
		||||
    const touchedTasksArray: Task<T>[] = options.touchedTasksArray;
 | 
			
		||||
    const touchedTasksArray: Task<T, TSteps>[] = options.touchedTasksArray;
 | 
			
		||||
 | 
			
		||||
    touchedTasksArray.push(taskToRun);
 | 
			
		||||
 | 
			
		||||
@@ -158,8 +171,8 @@ export class Task<T = undefined> {
 | 
			
		||||
  public execDelay: number;
 | 
			
		||||
  public timeout: number;
 | 
			
		||||
 | 
			
		||||
  public preTask: Task<T> | TPreOrAfterTaskFunction;
 | 
			
		||||
  public afterTask: Task<T> | TPreOrAfterTaskFunction;
 | 
			
		||||
  public preTask: Task<T, any> | TPreOrAfterTaskFunction;
 | 
			
		||||
  public afterTask: Task<T, any> | TPreOrAfterTaskFunction;
 | 
			
		||||
 | 
			
		||||
  // Add a list to store the blocking tasks
 | 
			
		||||
  public blockingTasks: Task[] = [];
 | 
			
		||||
@@ -171,6 +184,8 @@ export class Task<T = undefined> {
 | 
			
		||||
  public running: boolean = false;
 | 
			
		||||
  public bufferRunner = new BufferRunner(this);
 | 
			
		||||
  public cycleCounter = new CycleCounter(this);
 | 
			
		||||
  public lastRun?: Date;
 | 
			
		||||
  public runCount: number = 0;
 | 
			
		||||
 | 
			
		||||
  public get idle() {
 | 
			
		||||
    return !this.running;
 | 
			
		||||
@@ -179,15 +194,22 @@ export class Task<T = undefined> {
 | 
			
		||||
  public taskSetup: ITaskSetupFunction<T>;
 | 
			
		||||
  public setupValue: T;
 | 
			
		||||
 | 
			
		||||
  // Step tracking properties
 | 
			
		||||
  private steps = new Map<string, TaskStep>();
 | 
			
		||||
  private stepProgress = new Map<string, number>();
 | 
			
		||||
  public currentStepName?: string;
 | 
			
		||||
  private providedSteps?: TSteps;
 | 
			
		||||
 | 
			
		||||
  constructor(optionsArg: {
 | 
			
		||||
    taskFunction: ITaskFunction<T>;
 | 
			
		||||
    preTask?: Task<T> | TPreOrAfterTaskFunction;
 | 
			
		||||
    afterTask?: Task<T> | TPreOrAfterTaskFunction;
 | 
			
		||||
    preTask?: Task<T, any> | TPreOrAfterTaskFunction;
 | 
			
		||||
    afterTask?: Task<T, any> | TPreOrAfterTaskFunction;
 | 
			
		||||
    buffered?: boolean;
 | 
			
		||||
    bufferMax?: number;
 | 
			
		||||
    execDelay?: number;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    taskSetup?: ITaskSetupFunction<T>;
 | 
			
		||||
    steps?: TSteps;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.taskFunction = optionsArg.taskFunction;
 | 
			
		||||
    this.preTask = optionsArg.preTask;
 | 
			
		||||
@@ -198,6 +220,19 @@ export class Task<T = undefined> {
 | 
			
		||||
    this.name = optionsArg.name;
 | 
			
		||||
    this.taskSetup = optionsArg.taskSetup;
 | 
			
		||||
 | 
			
		||||
    // Initialize steps if provided
 | 
			
		||||
    if (optionsArg.steps) {
 | 
			
		||||
      this.providedSteps = optionsArg.steps;
 | 
			
		||||
      for (const stepConfig of optionsArg.steps) {
 | 
			
		||||
        const step = new TaskStep({
 | 
			
		||||
          name: stepConfig.name,
 | 
			
		||||
          description: stepConfig.description,
 | 
			
		||||
          percentage: stepConfig.percentage,
 | 
			
		||||
        });
 | 
			
		||||
        this.steps.set(stepConfig.name, step);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create the finished promise
 | 
			
		||||
    this.finished = new Promise((resolve) => {
 | 
			
		||||
      this.resolveFinished = resolve;
 | 
			
		||||
@@ -213,10 +248,102 @@ export class Task<T = undefined> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public triggerUnBuffered(x?: any): Promise<any> {
 | 
			
		||||
    return Task.runTask<T>(this, { x: x });
 | 
			
		||||
    return Task.runTask<T, TSteps>(this, { x: x });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public triggerBuffered(x?: any): Promise<any> {
 | 
			
		||||
    return this.bufferRunner.trigger(x);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Step notification method with typed step names
 | 
			
		||||
  public notifyStep(stepName: StepNames<TSteps>): void {
 | 
			
		||||
    // Complete previous step if exists
 | 
			
		||||
    if (this.currentStepName) {
 | 
			
		||||
      const prevStep = this.steps.get(this.currentStepName);
 | 
			
		||||
      if (prevStep && prevStep.status === 'active') {
 | 
			
		||||
        prevStep.complete();
 | 
			
		||||
        this.stepProgress.set(this.currentStepName, prevStep.percentage);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Start new step
 | 
			
		||||
    const step = this.steps.get(stepName as string);
 | 
			
		||||
    if (step) {
 | 
			
		||||
      step.start();
 | 
			
		||||
      this.currentStepName = stepName as string;
 | 
			
		||||
      
 | 
			
		||||
      // Emit event for frontend updates (could be enhanced with event emitter)
 | 
			
		||||
      if (this.name) {
 | 
			
		||||
        logger.log('info', `Task ${this.name}: Starting step "${stepName}" - ${step.description}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get current progress based on completed steps
 | 
			
		||||
  public getProgress(): number {
 | 
			
		||||
    let totalProgress = 0;
 | 
			
		||||
    for (const [stepName, percentage] of this.stepProgress) {
 | 
			
		||||
      totalProgress += percentage;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Add partial progress of current step if exists
 | 
			
		||||
    if (this.currentStepName) {
 | 
			
		||||
      const currentStep = this.steps.get(this.currentStepName);
 | 
			
		||||
      if (currentStep && currentStep.status === 'active') {
 | 
			
		||||
        // Could add partial progress calculation here if needed
 | 
			
		||||
        // For now, we'll consider active steps as 50% complete
 | 
			
		||||
        totalProgress += currentStep.percentage * 0.5;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return Math.min(100, Math.round(totalProgress));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get all steps metadata
 | 
			
		||||
  public getStepsMetadata(): ITaskStep[] {
 | 
			
		||||
    return Array.from(this.steps.values()).map(step => step.toJSON());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get task metadata
 | 
			
		||||
  public getMetadata(): ITaskMetadata {
 | 
			
		||||
    return {
 | 
			
		||||
      name: this.name || 'unnamed',
 | 
			
		||||
      version: this.version,
 | 
			
		||||
      status: this.running ? 'running' : 'idle',
 | 
			
		||||
      steps: this.getStepsMetadata(),
 | 
			
		||||
      currentStep: this.currentStepName,
 | 
			
		||||
      currentProgress: this.getProgress(),
 | 
			
		||||
      runCount: this.runCount,
 | 
			
		||||
      buffered: this.buffered,
 | 
			
		||||
      bufferMax: this.bufferMax,
 | 
			
		||||
      timeout: this.timeout,
 | 
			
		||||
      cronSchedule: this.cronJob?.cronExpression,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Reset all steps to pending state
 | 
			
		||||
  public resetSteps(): void {
 | 
			
		||||
    this.steps.forEach(step => step.reset());
 | 
			
		||||
    this.stepProgress.clear();
 | 
			
		||||
    this.currentStepName = undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Complete all remaining steps (useful for cleanup)
 | 
			
		||||
  private completeAllSteps(): void {
 | 
			
		||||
    if (this.currentStepName) {
 | 
			
		||||
      const currentStep = this.steps.get(this.currentStepName);
 | 
			
		||||
      if (currentStep && currentStep.status === 'active') {
 | 
			
		||||
        currentStep.complete();
 | 
			
		||||
        this.stepProgress.set(this.currentStepName, currentStep.percentage);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Mark any pending steps as completed (in case of early task completion)
 | 
			
		||||
    this.steps.forEach((step, name) => {
 | 
			
		||||
      if (step.status === 'pending') {
 | 
			
		||||
        // Don't add their percentage to progress since they weren't actually executed
 | 
			
		||||
        step.status = 'completed';
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								ts/taskbuffer.classes.taskstep.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								ts/taskbuffer.classes.taskstep.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
export interface ITaskStep {
 | 
			
		||||
  name: string;
 | 
			
		||||
  description: string;
 | 
			
		||||
  percentage: number; // Weight of this step (0-100)
 | 
			
		||||
  status: 'pending' | 'active' | 'completed';
 | 
			
		||||
  startTime?: number;
 | 
			
		||||
  endTime?: number;
 | 
			
		||||
  duration?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TaskStep implements ITaskStep {
 | 
			
		||||
  public name: string;
 | 
			
		||||
  public description: string;
 | 
			
		||||
  public percentage: number;
 | 
			
		||||
  public status: 'pending' | 'active' | 'completed' = 'pending';
 | 
			
		||||
  public startTime?: number;
 | 
			
		||||
  public endTime?: number;
 | 
			
		||||
  public duration?: number;
 | 
			
		||||
 | 
			
		||||
  constructor(config: { name: string; description: string; percentage: number }) {
 | 
			
		||||
    this.name = config.name;
 | 
			
		||||
    this.description = config.description;
 | 
			
		||||
    this.percentage = config.percentage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public start(): void {
 | 
			
		||||
    this.status = 'active';
 | 
			
		||||
    this.startTime = Date.now();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public complete(): void {
 | 
			
		||||
    if (this.startTime) {
 | 
			
		||||
      this.endTime = Date.now();
 | 
			
		||||
      this.duration = this.endTime - this.startTime;
 | 
			
		||||
    }
 | 
			
		||||
    this.status = 'completed';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public reset(): void {
 | 
			
		||||
    this.status = 'pending';
 | 
			
		||||
    this.startTime = undefined;
 | 
			
		||||
    this.endTime = undefined;
 | 
			
		||||
    this.duration = undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public toJSON(): ITaskStep {
 | 
			
		||||
    return {
 | 
			
		||||
      name: this.name,
 | 
			
		||||
      description: this.description,
 | 
			
		||||
      percentage: this.percentage,
 | 
			
		||||
      status: this.status,
 | 
			
		||||
      startTime: this.startTime,
 | 
			
		||||
      endTime: this.endTime,
 | 
			
		||||
      duration: this.duration,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								ts/taskbuffer.interfaces.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								ts/taskbuffer.interfaces.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import type { ITaskStep } from './taskbuffer.classes.taskstep.js';
 | 
			
		||||
 | 
			
		||||
export interface ITaskMetadata {
 | 
			
		||||
  name: string;
 | 
			
		||||
  version?: string;
 | 
			
		||||
  status: 'idle' | 'running' | 'completed' | 'failed';
 | 
			
		||||
  steps: ITaskStep[];
 | 
			
		||||
  currentStep?: string;
 | 
			
		||||
  currentProgress: number; // 0-100
 | 
			
		||||
  lastRun?: Date;
 | 
			
		||||
  nextRun?: Date; // For scheduled tasks
 | 
			
		||||
  runCount: number;
 | 
			
		||||
  averageDuration?: number;
 | 
			
		||||
  cronSchedule?: string;
 | 
			
		||||
  buffered?: boolean;
 | 
			
		||||
  bufferMax?: number;
 | 
			
		||||
  timeout?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ITaskExecutionReport {
 | 
			
		||||
  taskName: string;
 | 
			
		||||
  startTime: number;
 | 
			
		||||
  endTime: number;
 | 
			
		||||
  duration: number;
 | 
			
		||||
  steps: ITaskStep[];
 | 
			
		||||
  stepsCompleted: string[];
 | 
			
		||||
  progress: number;
 | 
			
		||||
  result?: any;
 | 
			
		||||
  error?: Error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IScheduledTaskInfo {
 | 
			
		||||
  name: string;
 | 
			
		||||
  schedule: string;
 | 
			
		||||
  nextRun: Date;
 | 
			
		||||
  lastRun?: Date;
 | 
			
		||||
  steps?: ITaskStep[];
 | 
			
		||||
  metadata?: ITaskMetadata;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user