59 lines
1.3 KiB
TypeScript
59 lines
1.3 KiB
TypeScript
import type { InvalidationEvent } from "./invalidation";
|
|
|
|
export interface RefreshScheduler {
|
|
connected(): void;
|
|
notified(event: InvalidationEvent): void;
|
|
dispose(): void;
|
|
}
|
|
|
|
export interface RefreshSchedulerOptions {
|
|
delayMs?: number;
|
|
duplicateWindowMs?: number;
|
|
now?: () => number;
|
|
}
|
|
|
|
export function createRefreshScheduler(
|
|
refresh: () => void,
|
|
options: RefreshSchedulerOptions = {},
|
|
): RefreshScheduler {
|
|
const delayMs = options.delayMs ?? 75;
|
|
const duplicateWindowMs = options.duplicateWindowMs ?? 2_000;
|
|
const now = options.now ?? Date.now;
|
|
let pending: ReturnType<typeof setTimeout> | undefined;
|
|
let lastEventKey: string | undefined;
|
|
let lastEventAt = 0;
|
|
|
|
const schedule = (event?: InvalidationEvent) => {
|
|
if (event) {
|
|
const key = `${event.type}:${event.id}:${event.version}`;
|
|
const timestamp = now();
|
|
if (
|
|
key === lastEventKey &&
|
|
timestamp - lastEventAt < duplicateWindowMs
|
|
) {
|
|
return;
|
|
}
|
|
lastEventKey = key;
|
|
lastEventAt = timestamp;
|
|
}
|
|
if (pending) return;
|
|
pending = setTimeout(() => {
|
|
pending = undefined;
|
|
refresh();
|
|
}, delayMs);
|
|
};
|
|
|
|
return {
|
|
connected() {
|
|
schedule();
|
|
},
|
|
notified(event) {
|
|
schedule(event);
|
|
},
|
|
dispose() {
|
|
if (pending) clearTimeout(pending);
|
|
pending = undefined;
|
|
},
|
|
};
|
|
}
|