/// import type WebSocket from 'ws'; import EventEmitter from 'events'; import { JobDefinition, Operation, OperationType } from '@nosana/sdk'; import { NodeRepository } from '../../repository/NodeRepository.js'; import { Provider } from '../../provider/Provider.js'; import { Flow } from '@nosana/sdk'; import { StatsBuffer } from './loggers/StatsBuffer.js'; export type TaskManagerOps = Array>; export type ExecutionContext = { group: string; ops: string[]; }; export type DependencyContext = { dependencies: string[]; dependents: string[]; }; export declare const StopReasons: { readonly COMPLETED: "completed"; readonly EXPIRED: "expired"; readonly STOPPED: "stopped"; readonly QUIT: "quit"; readonly UNKNOWN: "unknown"; readonly RESTART: "restart"; }; export type StopReason = (typeof StopReasons)[keyof typeof StopReasons]; export declare const Statuses: { readonly SUCCESS: "success"; readonly STOPPED: "stopped"; readonly FAILED: "failed"; }; export declare const OperationProgressStatuses: { readonly FINISHED: "finished"; readonly STOPPED: "stopped"; readonly FAILED: "failed"; readonly RUNNING: "running"; readonly RESTARTING: "restarting"; readonly STOPPING: "stopping"; readonly STARTING: "starting"; readonly WAITING: "waiting"; readonly PENDING: "pending"; readonly INIT: "init"; }; export type LogType = 'container' | 'info' | 'error'; export interface TaskStat { opId: string; timestamp: number; cpu: { cpu_percent: number; }; memory: { memory_usage: number; memory_limit: number; memory_percent: number; }; disk: { read: number; write: number; }; network: { received: number; sent: number; }; } export interface TaskLog { opId: string; group: string; type: LogType; timestamp: number; message: any; } export type OperationData = { host?: string; container_ip?: string; endpoint?: { [key: string]: string; }; deployment_endpoint?: string; results?: Record; }; type InterpolateFn = (value: T) => T; type InterpolateOpFn = (op: Operation) => Operation; export type GlobalDataStore = Record; export type GlobalStore = { job: string; host: string; project: string; frps_address: string; version: string; variables?: Record; }; export type Status = (typeof Statuses)[keyof typeof Statuses]; export default class TaskManager { protected provider: Provider; protected repository: NodeRepository; protected job: string; protected project: string; protected definition?: JobDefinition | undefined; /** * All operations defined in the Job Definition (JD). */ protected operations: TaskManagerOps | undefined; /** * The ordered execution plan built from the job definition. * Each item represents a group of ops that run together horizontally. */ protected executionPlan: ExecutionContext[]; /** * this creates a map to assign the dependency */ protected dependecyMap: Map; /** * A map for fast lookup of operations by their ID. * Useful during validation and execution. */ protected opMap: Map>; /** * Global data store. * * This allows operations to reference data produced by others using literals like: * "%%global.frps_address%%" * "%%global.project%%" * * Supported keys: * - frps_address: The current FRPS address. * - project: The project public key of the jobs project. */ protected globalStore: GlobalStore; /** * Global data store for all operations in a job definition. * * This allows operations to reference data produced by others using literals like: * "%%ops.nginx-1.results.someKey%%" * "%%ops.nginx-1.host%%" * * Supported keys: * - result: Stores the output of an operation so it can be accessed later, even if it was unknown at the start of the job. * - host: Stores the reachable host/URL of the operation. Since Docker container names are dynamic, * this ensures other operations can communicate with it without needing to know the name in advance. */ protected globalOpStore: GlobalDataStore; /** * Main controller to allow global cancellation of the entire task flow. * All per-op controllers should eventually be tied to this as their parent, * or fallback to this if no specific controller is assigned yet. */ protected mainAbortController: AbortController; /** * Stores one AbortController per op. * Used to signal cancellation to all ops in a group or individually if needed. */ protected abortControllerMap: Map; /** * Keeps track of the currently running group. * Useful for coordinating group-level logic or logging. */ protected currentGroup: string | undefined; /** * keeps track of all promises of the current running group */ protected currentGroupOperationsPromises: Map>; /** * this is to create concurrency control on the operations */ protected lockedOperations: Map; /** * this is used to track the operations statuses */ protected operationStatus: Map; /** * this is to track event emitter to emit events */ protected operationsEventEmitters: Map; /** * save log buffer for streaming logs */ protected opLogBuffers: Map; /** * save stat buffer for streaming container stats */ protected opStatBuffers: Map; /** * this list of ws sub to the task managers events */ protected subscribers: Set; /** * stores filters */ protected logMatchers: Map boolean>; /** * ws subscribers for stats streaming */ protected statSubscribers: Set; /** * stores stat filters */ protected statMatchers: Map boolean>; protected TOTAL_LOGS_COUNT: number; /** * Lifecycle status of the task manager. * Can be 'init', 'running', 'stopped', or 'done'. */ protected status: string; private currentRunningStartPromise?; /** * Event emitter for task- and op-level lifecycle events. */ protected events: EventEmitter; constructor(provider: Provider, repository: NodeRepository, job: string, project: string, definition?: JobDefinition | undefined); runTaskManagerOperation: (flow: Flow, op: Operation, dependent: string[]) => Promise; restartTaskManagerOperation: (group: string, opId: string) => Promise; stopTaskManagerOperation: (group: string, opId: string) => Promise; stopTaskManagerGroupOperations: (group: string) => Promise; restartTaskManagerGroupOperations: (group: string) => Promise; stopAllTaskManagerOperations: (reason: StopReason) => void; createOperationMap: () => Map>; createExecutionPlan: () => ExecutionContext[]; createDependencyMap: () => Map; validateExecutionPlan: () => void; getOperationsStatus: () => Record; getOperationStatus: (id: string) => Record; getCurrentGroup: () => string | undefined; getCurrentGroupStatus: () => Record; getGroupStatus: (group: string) => Record; addlog: (log: TaskLog) => void; getLogsByOp: (opid: string) => TaskLog[]; getLogsByGroup: (group: string) => TaskLog[]; getAllLogs: () => TaskLog[]; subscribe: (ws: WebSocket, matcher: (log: TaskLog) => boolean) => void; unsubscribe: (ws: WebSocket) => void; addStat: (stat: TaskStat) => void; getStatsByOp: (opId: string) => TaskStat[]; getAllStats: () => TaskStat[]; queryStats: (start?: number, end?: number, intervalMs?: number) => TaskStat[]; getLatestStatPerOp: (since: number) => TaskStat[]; subscribeStats: (ws: WebSocket, matcher: (stat: TaskStat) => boolean) => void; unsubscribeStats: (ws: WebSocket) => void; setResult: (opId: string, key: string, value: any) => void; setResults: (opId: string, values: Record) => void; setHost: (opId: string, host: string) => void; setContainerIp: (opId: string, ip: string) => void; setDefaults: (flowId: string, project: string, jobDefinition: JobDefinition) => void; rehydrateEndpointsForOperation: (flowId: string, project: string, jobDefinition: JobDefinition, opId: string) => void; getByPath: (opId: string, path: string) => any; resolveLiteralsInString: (input: string) => string; interpolate: InterpolateFn; interpolateOperation: InterpolateOpFn; transformCollections: InterpolateOpFn; /** * Returns the unified event emitter for this task manager. */ getEventsEmitter(): EventEmitter; /** * Registers an op-level emitter and relays its relevant events to the * task-level unified emitter. Also tracks the emitter in operationsEventEmitters. */ protected registerAndRelayOpEmitter(opId: string, emitter: EventEmitter): void; /** * Prepares the TaskManager for execution by performing all necessary setup steps. * * This method performs two key operations: * * `build()`: * - Generates a map of all operations for fast lookup. * - Creates the execution plan based on operation groups and dependencies. * - Validates the structure of the plan to catch any misconfigurations early. * * `init()`: * - Initializes the task's persistent flow state in the repository. * - Skips initialization if the flow already exists (resumable/restartable design). * * Call this once before `start()` to ensure the task manager is fully ready. */ bootstrap(): void; /** * Starts the execution of the job by processing each group in the execution plan. * Tracks the full lifecycle including flow state updates and dynamic operations. * * Execution Lifecycle: * - If already started, returns the tracked lifecycle promise. * - Sets status to 'running' and updates the repository with start time. * - Iterates through execution groups and runs each op concurrently. * - Uses a dynamic while-loop to ensure all ops (including restarts) finish before advancing. * - After all groups finish, sets the final flow status: 'failed' if any op * failed, 'success' otherwise (including stopped/expired jobs). * - On any uncaught failure, marks the flow as 'failed' with end time. */ start(): Promise; /** * Gracefully stops the task manager and all its operations. * * This method: * - Immediately aborts all running operations by triggering the main abort controller. * - Waits for the current `start()` flow to finish, including database updates. * - Ensures `stop()` logic runs only once per job to prevent race conditions. * * Important: * - `stopAllTaskManagerOperations()` is synchronous and triggers cancellation. * - The actual cleanup and final state update (e.g., setting `endTime`) is handled * by the `start()` method’s final logic. */ stop(reason: StopReason): Promise; protected setUpOperationFunc(flow: Flow, id: string, dependent: string[]): Promise; protected trackGroupOperationPromise(opId: string, promise: Promise): Promise; protected getStatus(reason: StopReason, type: 'ops' | 'flow'): Status; protected getOpStateIndex(opId: string): number; private build; private init; } export {};