mistykey/packages/backend/src/misc/collapsed-queue.ts

58 lines
1.5 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { TimeService, TimerHandle } from '@/global/TimeService.js';
type Job<V> = {
value: V;
timer: TimerHandle;
};
// TODO: redis使えるようにする
export class CollapsedQueue<K, V> {
private jobs: Map<K, Job<V>> = new Map();
constructor(
public readonly name: string,
protected readonly timeService: TimeService,
private readonly timeout: number,
private readonly collapse: (oldValue: V, newValue: V) => V,
private readonly perform: (key: K, value: V) => Promise<void | unknown>,
private readonly onError?: (queue: CollapsedQueue<K, V>, error: unknown) => void,
) {}
enqueue(key: K, value: V) {
if (this.jobs.has(key)) {
const old = this.jobs.get(key)!;
const merged = this.collapse(old.value, value);
this.jobs.set(key, { ...old, value: merged });
} else {
const timer = this.timeService.startTimer(() => {
const job = this.jobs.get(key)!;
this.jobs.delete(key);
this._perform(key, job.value);
}, this.timeout);
this.jobs.set(key, { value, timer });
}
}
async performAllNow() {
const entries = [...this.jobs.entries()];
this.jobs.clear();
for (const [_key, job] of entries) {
this.timeService.stopTimer(job.timer);
}
return await Promise.allSettled(entries.map(([key, job]) => this._perform(key, job.value)));
}
private async _perform(key: K, value: V) {
try {
await this.perform(key, value);
} catch (err) {
this.onError?.(this, err);
throw err;
}
}
}