import { assertAccessorDecorator } from "../common/decorators.js"; import type { AsyncMethod } from "../common/types.js"; import { resolveCallable } from "../common/utils.js"; export interface RefreshableConfig { dataProvider: AsyncMethod | keyof This; intervalMs: number; } export function refreshable(config: RefreshableConfig) { return function( value: ClassAccessorDecoratorTarget, context: ClassAccessorDecoratorContext, ) { assertAccessorDecorator("refreshable", value, context); const intervalHandlers = new WeakMap>(); const updateValue = async function(this: This): Promise { const dataProvider = resolveCallable>(this, config.dataProvider); const data = await dataProvider(); value.set.call(this, data as Value | null); }; context.addInitializer(function(this: This): void { const intervalHandler = setInterval(() => { void updateValue.call(this); }, config.intervalMs); if (typeof (intervalHandler as { unref?: () => void; }).unref === "function") { intervalHandler.unref(); } intervalHandlers.set(this as object, intervalHandler); setTimeout(() => { void updateValue.call(this); }, 0); }); return { get(this: This): Value | null { return value.get.call(this); }, set(this: This, nextValue: Value | null): void { if (nextValue === null) { const intervalHandler = intervalHandlers.get(this as object); if (intervalHandler !== undefined) { clearInterval(intervalHandler); intervalHandlers.delete(this as object); } } }, init(initialValue: Value | null): Value | null { return initialValue; }, }; }; }