Skip to main content
Glama
operation.ts7.07 kB
import { v4 as uuidv4 } from 'uuid'; import { OperationExecutor, OperationOptions, OperationResult, OperationStatus, ProgressCallback } from './types'; import logger from '../utils/logger'; import { timeout, createCancelablePromise, CancelablePromise } from './timeout-util'; /** * Operation class * * Represents an asynchronous operation that can be executed, cancelled, and report progress. */ export class Operation<T> { private readonly logId: string; private readonly executor: OperationExecutor<T>; private readonly options: OperationOptions; private readonly abortController: AbortController; private cancelablePromise: CancelablePromise<T> | null = null; private timeoutPromise: Promise<T> | null = null; private latestProgress: any = null; private startTime: number = 0; private endTime: number = 0; private status: OperationStatus = OperationStatus.RUNNING; private result: T | undefined; private error: Error | null = null; /** * Constructor * * @param executor Function that executes the operation * @param options Operation options */ constructor(executor: OperationExecutor<T>, options: OperationOptions) { this.logId = uuidv4(); this.executor = executor; this.options = options; this.abortController = new AbortController(); } /** * Execute the operation * * @returns Promise that resolves with the operation result */ public async execute(): Promise<OperationResult<T>> { this.startTime = Date.now(); this.status = OperationStatus.RUNNING; try { // Create a cancelable promise this.cancelablePromise = createCancelablePromise<T>((resolve, reject, onCancel) => { // Handle cancellation onCancel(() => { this.abortController.abort(); this.status = OperationStatus.CANCELLED; this.endTime = Date.now(); logger.info(`Operation cancelled: ${this.logId}`); }); // Create a progress callback const onProgress: ProgressCallback = (progress) => { try { this.latestProgress = progress; if (this.options.onProgress) { this.options.onProgress(progress); } } catch (error) { logger.error(`Error in progress callback: ${error instanceof Error ? error.message : String(error)}`); } }; // Execute the operation this.executor({ onProgress, signal: this.abortController.signal }) .then(resolve) .catch(reject); }); // Create a timeout promise this.timeoutPromise = timeout(this.cancelablePromise, { milliseconds: this.options.timeoutMs, message: `Operation timed out after ${this.options.timeoutMs}ms` }); // Execute the operation with timeout try { this.result = await this.timeoutPromise; this.status = OperationStatus.SUCCESS; this.endTime = Date.now(); return this.createSuccessResult(); } catch (error) { // Check if the error is a timeout error if (error instanceof Error && error.message.includes('timed out')) { this.status = OperationStatus.TIMEOUT; return this.createTimeoutResult(); } // Handle other errors this.error = error instanceof Error ? error : new Error(String(error)); this.status = OperationStatus.ERROR; this.endTime = Date.now(); return this.createErrorResult(); } } catch (error) { // Handle unexpected errors this.error = error instanceof Error ? error : new Error(String(error)); this.status = OperationStatus.ERROR; this.endTime = Date.now(); return this.createErrorResult(); } } /** * Cancel the operation */ public cancel(): void { if (this.cancelablePromise) { this.cancelablePromise.cancel(); } } /** * Get the operation result * * @returns Operation result */ public getResult(): OperationResult<T> { switch (this.status) { case OperationStatus.SUCCESS: return this.createSuccessResult(); case OperationStatus.ERROR: return this.createErrorResult(); case OperationStatus.TIMEOUT: return this.createTimeoutResult(); case OperationStatus.CANCELLED: return this.createCancelledResult(); case OperationStatus.RUNNING: default: return this.createRunningResult(); } } /** * Get the operation log ID * * @returns Operation log ID */ public getLogId(): string { return this.logId; } /** * Get the operation status * * @returns Operation status */ public getStatus(): OperationStatus { return this.status; } /** * Check if the operation is complete * * @returns True if the operation is complete, false otherwise */ public isComplete(): boolean { return this.status !== OperationStatus.RUNNING; } /** * Get the latest progress * * @returns Latest progress */ public getLatestProgress(): any { return this.latestProgress; } /** * Create a success result * * @returns Success result */ private createSuccessResult(): OperationResult<T> { return { status: OperationStatus.SUCCESS, logId: this.logId, result: this.result, isComplete: true, startTime: this.startTime, endTime: this.endTime, message: 'Operation completed successfully' }; } /** * Create an error result * * @returns Error result */ private createErrorResult(): OperationResult<T> { return { status: OperationStatus.ERROR, logId: this.logId, error: this.error?.message || 'Unknown error', isComplete: true, startTime: this.startTime, endTime: this.endTime }; } /** * Create a timeout result * * @returns Timeout result */ private createTimeoutResult(): OperationResult<T> { return { status: OperationStatus.TIMEOUT, logId: this.logId, partialResult: this.latestProgress, isComplete: false, startTime: this.startTime, message: `Operation timed out after ${this.options.timeoutMs}ms, but is still running in the background` }; } /** * Create a cancelled result * * @returns Cancelled result */ private createCancelledResult(): OperationResult<T> { return { status: OperationStatus.CANCELLED, logId: this.logId, partialResult: this.latestProgress, isComplete: true, startTime: this.startTime, endTime: this.endTime, message: 'Operation was cancelled' }; } /** * Create a running result * * @returns Running result */ private createRunningResult(): OperationResult<T> { return { status: OperationStatus.RUNNING, logId: this.logId, partialResult: this.latestProgress, isComplete: false, startTime: this.startTime, message: 'Operation is still running' }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TSavo/Unity-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server