mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2025-12-24 16:54:21 +01:00
139 lines
3.1 KiB
TypeScript
139 lines
3.1 KiB
TypeScript
import { ulid } from "ulid";
|
|
|
|
export type Task = {
|
|
key: string;
|
|
execute: () => Promise<void>;
|
|
};
|
|
|
|
type TaskStatus = {
|
|
id: string;
|
|
task: Task;
|
|
} & (
|
|
| {
|
|
status: "pending" | "completed" | "failed";
|
|
}
|
|
| {
|
|
status: "running";
|
|
promise: Promise<void>;
|
|
}
|
|
);
|
|
|
|
type Options = {
|
|
maxConcurrency: number;
|
|
};
|
|
|
|
export class TaskExecutor {
|
|
private taskStatuses: TaskStatus[] = [];
|
|
private executionPromise?: {
|
|
resolve: () => void;
|
|
reject: (reason?: unknown) => void;
|
|
promise: Promise<void>;
|
|
};
|
|
private options: Options;
|
|
|
|
constructor(options?: Partial<Options>) {
|
|
this.options = {
|
|
maxConcurrency: 10,
|
|
...options,
|
|
};
|
|
}
|
|
|
|
private setExecutionPromise() {
|
|
let resolveExecution: (() => void) | undefined;
|
|
let rejectExecution: ((reason?: unknown) => void) | undefined;
|
|
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
resolveExecution = resolve;
|
|
rejectExecution = reject;
|
|
});
|
|
|
|
if (resolveExecution === undefined || rejectExecution === undefined) {
|
|
throw new Error("Illegal state: Promise not created");
|
|
}
|
|
|
|
this.executionPromise = {
|
|
resolve: resolveExecution,
|
|
reject: rejectExecution,
|
|
promise,
|
|
};
|
|
}
|
|
|
|
public setTasks(tasks: Task[]) {
|
|
const newTaskStatuses: TaskStatus[] = tasks.map((task) => ({
|
|
id: `${task.key}-${ulid()}`,
|
|
status: "pending",
|
|
task,
|
|
}));
|
|
|
|
this.taskStatuses.push(...newTaskStatuses);
|
|
}
|
|
|
|
private get pendingTasks() {
|
|
return this.taskStatuses.filter((task) => task.status === "pending");
|
|
}
|
|
|
|
private get runningTasks() {
|
|
return this.taskStatuses.filter((task) => task.status === "running");
|
|
}
|
|
|
|
private updateStatus(id: string, status: TaskStatus) {
|
|
const found = this.taskStatuses.find((task) => task.id === id);
|
|
|
|
if (!found) {
|
|
throw new Error(`Task not found: ${id}`);
|
|
}
|
|
|
|
Object.assign(found, status);
|
|
}
|
|
|
|
public async execute() {
|
|
this.setExecutionPromise();
|
|
this.refresh();
|
|
await this.executionPromise?.promise;
|
|
}
|
|
|
|
private refresh() {
|
|
if (this.runningTasks.length === 0 && this.pendingTasks.length === 0) {
|
|
this.executionPromise?.resolve();
|
|
console.log("execution completed.");
|
|
return;
|
|
}
|
|
|
|
const remainingTaskCount =
|
|
this.options.maxConcurrency - this.runningTasks.length;
|
|
|
|
if (remainingTaskCount <= 0) {
|
|
return;
|
|
}
|
|
|
|
for (const task of this.pendingTasks.slice(0, remainingTaskCount)) {
|
|
this.updateStatus(task.id, {
|
|
id: task.id,
|
|
status: "running",
|
|
task: task.task,
|
|
promise: (async () => {
|
|
try {
|
|
await task.task.execute();
|
|
|
|
this.updateStatus(task.id, {
|
|
id: task.id,
|
|
status: "completed",
|
|
task: task.task,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
this.updateStatus(task.id, {
|
|
id: task.id,
|
|
status: "failed",
|
|
task: task.task,
|
|
});
|
|
} finally {
|
|
this.refresh();
|
|
}
|
|
})(),
|
|
});
|
|
}
|
|
}
|
|
}
|