export var ResumableTaskName;
(function (ResumableTaskName) {
    ResumableTaskName["RecordsDelete"] = "RecordsDelete";
    ResumableTaskName["RecordsSquash"] = "RecordsSquash";
})(ResumableTaskName || (ResumableTaskName = {}));
export class ResumableTaskManager {
    resumableTaskStore;
    /**
     * The frequency at which the automatic timeout extension is requested for a resumable task.
     */
    static timeoutExtensionFrequencyInSeconds = 30;
    resumableTaskBatchSize = 100;
    resumableTaskHandlers;
    constructor(resumableTaskStore, storageController) {
        this.resumableTaskStore = resumableTaskStore;
        // assign resumable task handlers
        this.resumableTaskHandlers = {
            // NOTE: The arrow function is IMPORTANT here, else the `this` context will be lost within the invoked method.
            // e.g. code within performRecordsDelete() won't know `this` refers to the `storageController` instance.
            [ResumableTaskName.RecordsDelete]: async (task) => storageController.performRecordsDelete(task),
            [ResumableTaskName.RecordsSquash]: async (task) => storageController.performRecordsSquash(task),
        };
    }
    /**
     * Runs a new resumable task.
     */
    async run(task) {
        const timeoutInSeconds = ResumableTaskManager.timeoutExtensionFrequencyInSeconds * 2; // give ample time for extension to take place
        // register the new resumable task before running it so that it can be resumed if it times out for any reason
        const managedResumableTask = await this.resumableTaskStore.register(task, timeoutInSeconds);
        return this.runWithAutomaticTimeoutExtension(managedResumableTask);
    }
    /**
     * Runs a resumable task with automatic timeout extension.
     * Deletes the task from the resumable task store once it is completed.
     */
    async runWithAutomaticTimeoutExtension(managedTask) {
        const timeoutInSeconds = ResumableTaskManager.timeoutExtensionFrequencyInSeconds * 2; // give ample time for extension to take place
        let timer;
        try {
            // start a timer loop to keep extending the timeout of the task until it is completed
            timer = setInterval(() => {
                this.resumableTaskStore.extend(managedTask.id, timeoutInSeconds);
            }, ResumableTaskManager.timeoutExtensionFrequencyInSeconds * 1000);
            const handler = this.resumableTaskHandlers[managedTask.task.name];
            const result = await handler(managedTask.task.data);
            await this.resumableTaskStore.delete(managedTask.id);
            return result;
        }
        finally {
            ResumableTaskManager.clearTimeoutExtensionTimer(timer);
        }
    }
    /**
     * Removes the specified timeout extension loop timer.
     * NOTE: created mainly for testing purposes so we can spy on this specific method without needing to filter out other `clearInterval` calls.
     * NOTE: using `ReturnType` utility type to avoid using node.js specific type, because `setInterval` returns a `number` in browser environments.
     */
    static clearTimeoutExtensionTimer(timer) {
        clearInterval(timer);
    }
    /**
     * Resumes the execution of resumable tasks until all are completed successfully.
     */
    async resumeTasksAndWaitForCompletion() {
        while (true) {
            const resumableTasks = await this.resumableTaskStore.grab(this.resumableTaskBatchSize);
            if (resumableTasks === undefined || resumableTasks.length === 0) {
                break;
            }
            // Handle this batch of tasks before grabbing the next batch.
            await this.retryTasksUntilCompletion(resumableTasks);
        }
    }
    /**
     * Repeatedly retry the given tasks until all are completed successfully.
     * Tasks are processed sequentially to avoid concurrent database write contention (e.g. SQLite's single-writer lock).
     */
    async retryTasksUntilCompletion(resumableTasks) {
        let managedTasks = resumableTasks;
        while (managedTasks.length > 0) {
            const managedTasksCopy = managedTasks;
            managedTasks = [];
            for (const managedTask of managedTasksCopy) {
                try {
                    await this.runWithAutomaticTimeoutExtension(managedTask);
                }
                catch (error) {
                    console.error('Error while running resumable task:', error);
                    console.error('Resumable task:', managedTask);
                    managedTasks.push(managedTask);
                }
            }
        }
    }
}
//# sourceMappingURL=resumable-task-manager.js.map