import 'dart:async'; enum RateLimitMode { debounce, throttle } class ThrottleOptions { const ThrottleOptions({this.leading = true, this.trailing = true}); final bool leading; final bool trailing; } class DebounceThrottle { DebounceThrottle({ required this.duration, required this.mode, this.onCallback, this.throttleOptions = const ThrottleOptions(), }); final Duration duration; final RateLimitMode mode; final void Function(T value)? onCallback; final ThrottleOptions throttleOptions; Timer? _timer; T? _lastValue; DateTime? _lastExecuteTime; bool _waitingTrailing = false; void call(T value) { _lastValue = value; switch (mode) { case RateLimitMode.debounce: _debounce(value); case RateLimitMode.throttle: _throttle(value); } } void _debounce(T value) { _timer?.cancel(); _timer = Timer(duration, () { onCallback?.call(value); _lastExecuteTime = DateTime.now(); }); } void _throttle(T value) { final now = DateTime.now(); final last = _lastExecuteTime; final inWindow = last != null && now.difference(last) < duration; if (!inWindow && throttleOptions.leading) { onCallback?.call(value); _lastExecuteTime = now; _waitingTrailing = false; _timer?.cancel(); return; } if (!throttleOptions.trailing || _waitingTrailing) return; _waitingTrailing = true; final remaining = last == null ? duration : duration - now.difference(last); _timer?.cancel(); _timer = Timer(remaining, () { final value = _lastValue; if (value != null) onCallback?.call(value); _lastExecuteTime = DateTime.now(); _waitingTrailing = false; }); } void flush() { _timer?.cancel(); final value = _lastValue; if (value != null) { onCallback?.call(value); _lastExecuteTime = DateTime.now(); } _lastValue = null; _waitingTrailing = false; } void cancel() { _timer?.cancel(); _lastValue = null; _waitingTrailing = false; } void dispose() => cancel(); } class RateLimitHub { RateLimitHub({this.removeDebouncerOnDone = true}); final bool removeDebouncerOnDone; final Map> _debouncers = {}; final Map> _throttlers = {}; void debounce({ required Object key, required T value, Duration duration = const Duration(milliseconds: 300), required void Function(T value) onCallback, }) { _debouncers[key]?.cancel(); late DebounceThrottle limiter; limiter = DebounceThrottle( duration: duration, mode: RateLimitMode.debounce, onCallback: (value) { onCallback(value); if (removeDebouncerOnDone) _debouncers.remove(key); }, ); _debouncers[key] = limiter; limiter(value); } void throttle({ required Object key, required T value, Duration duration = const Duration(milliseconds: 300), ThrottleOptions options = const ThrottleOptions(), required void Function(T value) onCallback, }) { _throttlers.putIfAbsent( key, () => DebounceThrottle( duration: duration, mode: RateLimitMode.throttle, throttleOptions: options, onCallback: onCallback, ) as DebounceThrottle, ); (_throttlers[key] as DebounceThrottle)(value); } void cancel(Object key) { _debouncers.remove(key)?.cancel(); _throttlers.remove(key)?.cancel(); } void clear() { for (final limiter in _debouncers.values) { limiter.dispose(); } for (final limiter in _throttlers.values) { limiter.dispose(); } _debouncers.clear(); _throttlers.clear(); } } class RateLimit { RateLimit._(); static final instance = RateLimitHub(); }