From be1f0befbcdbb466ef3327d29bc7deb75f035af7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:14:05 +0000 Subject: [PATCH 1/5] Initial plan From 9604cce35b5691a759a7163597848163ab3aa21d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:30:52 +0000 Subject: [PATCH 2/5] refactor: extract duplicated rate-limiting code into factory functions Co-authored-by: liuyib <38221479+liuyib@users.noreply.github.com> --- .../createRateLimitEffect.ts | 39 +++++++++++ .../createRateLimitHooks/createRateLimitFn.ts | 67 +++++++++++++++++++ .../createRateLimitValue.ts | 33 +++++++++ .../hooks/src/createRateLimitHooks/index.ts | 4 ++ packages/hooks/src/useDebounce/index.ts | 19 ++---- packages/hooks/src/useDebounceEffect/index.ts | 23 +------ packages/hooks/src/useDebounceFn/index.ts | 41 +----------- .../useInfiniteScroll/__tests__/index.spec.ts | 10 +-- packages/hooks/src/useThrottle/index.ts | 19 ++---- packages/hooks/src/useThrottleEffect/index.ts | 23 +------ packages/hooks/src/useThrottleFn/index.ts | 41 +----------- 11 files changed, 169 insertions(+), 150 deletions(-) create mode 100644 packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts create mode 100644 packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts create mode 100644 packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts create mode 100644 packages/hooks/src/createRateLimitHooks/index.ts diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts new file mode 100644 index 0000000000..93a674488a --- /dev/null +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; +import type { DependencyList, EffectCallback } from 'react'; +import useUpdateEffect from '../useUpdateEffect'; +import type { RateLimitOptions } from './createRateLimitFn'; + +type noop = (...args: any[]) => any; + +export function createRateLimitEffect( + useRateLimitFn: ( + fn: T, + options?: RateLimitOptions, + ) => { + run: T; + cancel: () => void; + flush: () => void; + }, +) { + return function useRateLimitEffect( + effect: EffectCallback, + deps?: DependencyList, + options?: RateLimitOptions, + ) { + const [flag, setFlag] = useState({}); + + const { run } = useRateLimitFn( + (() => { + setFlag({}); + }) as T, + options, + ); + + // biome-ignore lint/correctness/useExhaustiveDependencies: deps is intentionally passed through from the user + useEffect(() => { + return run(); + }, deps); + + useUpdateEffect(effect, [flag]); + }; +} diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts new file mode 100644 index 0000000000..c2ed8f49b8 --- /dev/null +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts @@ -0,0 +1,67 @@ +import { useMemo } from 'react'; +import useLatest from '../useLatest'; +import useUnmount from '../useUnmount'; +import { isFunction } from '../utils'; +import isDev from '../utils/isDev'; + +type noop = (...args: any[]) => any; + +export interface RateLimitOptions { + wait?: number; + leading?: boolean; + trailing?: boolean; + maxWait?: number; +} + +export interface RateLimitFunction { + (...args: Parameters): ReturnType; + cancel: () => void; + flush: () => void; +} + +export function createRateLimitFn( + rateLimitFn: ( + func: (...args: Parameters) => ReturnType, + wait: number, + options?: RateLimitOptions, + ) => RateLimitFunction, + hookName: string, +) { + return function useRateLimitFn(fn: T, options?: RateLimitOptions) { + if (isDev) { + if (!isFunction(fn)) { + console.error(`${hookName} expected parameter is a function, got ${typeof fn}`); + } + } + + const fnRef = useLatest(fn); + + const wait = options?.wait ?? 1000; + + // Note: We intentionally use an empty dependency array here. + // The rateLimitFn is created once and captures the latest fn via fnRef.current + // eslint-disable-next-line react-hooks/exhaustive-deps + const rateLimited = useMemo( + () => + rateLimitFn( + (...args: Parameters): ReturnType => { + return fnRef.current(...args); + }, + wait, + options, + ), + // biome-ignore lint/correctness/useExhaustiveDependencies: rateLimitFn is stable, fnRef updates are captured via .current + [], + ); + + useUnmount(() => { + rateLimited.cancel(); + }); + + return { + run: rateLimited, + cancel: rateLimited.cancel, + flush: rateLimited.flush, + }; + }; +} diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts new file mode 100644 index 0000000000..be439d2d1d --- /dev/null +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; +import type { RateLimitOptions } from './createRateLimitFn'; + +type noop = (...args: any[]) => any; + +export function createRateLimitValue( + useRateLimitFn: ( + fn: T, + options?: RateLimitOptions, + ) => { + run: T; + cancel: () => void; + flush: () => void; + }, +) { + return function useRateLimitValue(value: V, options?: RateLimitOptions) { + const [rateLimited, setRateLimited] = useState(value); + + const { run } = useRateLimitFn( + (() => { + setRateLimited(value); + }) as T, + options, + ); + + // biome-ignore lint/correctness/useExhaustiveDependencies: run is stable, we only want to trigger on value changes + useEffect(() => { + run(); + }, [value]); + + return rateLimited; + }; +} diff --git a/packages/hooks/src/createRateLimitHooks/index.ts b/packages/hooks/src/createRateLimitHooks/index.ts new file mode 100644 index 0000000000..4cc83b8206 --- /dev/null +++ b/packages/hooks/src/createRateLimitHooks/index.ts @@ -0,0 +1,4 @@ +export { createRateLimitFn } from './createRateLimitFn'; +export type { RateLimitOptions, RateLimitFunction } from './createRateLimitFn'; +export { createRateLimitValue } from './createRateLimitValue'; +export { createRateLimitEffect } from './createRateLimitEffect'; diff --git a/packages/hooks/src/useDebounce/index.ts b/packages/hooks/src/useDebounce/index.ts index baff02e742..b83c2dca7d 100644 --- a/packages/hooks/src/useDebounce/index.ts +++ b/packages/hooks/src/useDebounce/index.ts @@ -1,19 +1,8 @@ -import { useEffect, useState } from 'react'; -import useDebounceFn from '../useDebounceFn'; import type { DebounceOptions } from './debounceOptions'; +import useDebounceFn from '../useDebounceFn'; +import { createRateLimitValue } from '../createRateLimitHooks'; -function useDebounce(value: T, options?: DebounceOptions) { - const [debounced, setDebounced] = useState(value); - - const { run } = useDebounceFn(() => { - setDebounced(value); - }, options); - - useEffect(() => { - run(); - }, [value]); - - return debounced; -} +const useDebounce = createRateLimitValue(useDebounceFn); export default useDebounce; +export type { DebounceOptions }; diff --git a/packages/hooks/src/useDebounceEffect/index.ts b/packages/hooks/src/useDebounceEffect/index.ts index 2866d91d98..a21976df84 100644 --- a/packages/hooks/src/useDebounceEffect/index.ts +++ b/packages/hooks/src/useDebounceEffect/index.ts @@ -1,25 +1,8 @@ -import { useEffect, useState } from 'react'; -import type { DependencyList, EffectCallback } from 'react'; import type { DebounceOptions } from '../useDebounce/debounceOptions'; import useDebounceFn from '../useDebounceFn'; -import useUpdateEffect from '../useUpdateEffect'; +import { createRateLimitEffect } from '../createRateLimitHooks'; -function useDebounceEffect( - effect: EffectCallback, - deps?: DependencyList, - options?: DebounceOptions, -) { - const [flag, setFlag] = useState({}); - - const { run } = useDebounceFn(() => { - setFlag({}); - }, options); - - useEffect(() => { - return run(); - }, deps); - - useUpdateEffect(effect, [flag]); -} +const useDebounceEffect = createRateLimitEffect(useDebounceFn); export default useDebounceEffect; +export type { DebounceOptions }; diff --git a/packages/hooks/src/useDebounceFn/index.ts b/packages/hooks/src/useDebounceFn/index.ts index 24910f4b37..393c9f4ec4 100644 --- a/packages/hooks/src/useDebounceFn/index.ts +++ b/packages/hooks/src/useDebounceFn/index.ts @@ -1,45 +1,10 @@ import { debounce } from '../utils/lodash-polyfill'; -import { useMemo } from 'react'; import type { DebounceOptions } from '../useDebounce/debounceOptions'; -import useLatest from '../useLatest'; -import useUnmount from '../useUnmount'; -import { isFunction } from '../utils'; -import isDev from '../utils/isDev'; +import { createRateLimitFn } from '../createRateLimitHooks'; type noop = (...args: any[]) => any; -function useDebounceFn(fn: T, options?: DebounceOptions) { - if (isDev) { - if (!isFunction(fn)) { - console.error(`useDebounceFn expected parameter is a function, got ${typeof fn}`); - } - } - - const fnRef = useLatest(fn); - - const wait = options?.wait ?? 1000; - - const debounced = useMemo( - () => - debounce( - (...args: Parameters): ReturnType => { - return fnRef.current(...args); - }, - wait, - options, - ), - [], - ); - - useUnmount(() => { - debounced.cancel(); - }); - - return { - run: debounced, - cancel: debounced.cancel, - flush: debounced.flush, - }; -} +const useDebounceFn = createRateLimitFn(debounce, 'useDebounceFn'); export default useDebounceFn; +export type { DebounceOptions }; diff --git a/packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts b/packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts index 717fe7b8cb..cbbb3903dc 100644 --- a/packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts +++ b/packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts @@ -41,10 +41,12 @@ describe('useInfiniteScroll', () => { beforeAll(() => { vi.useFakeTimers(); // Mock requestAnimationFrame to execute callbacks immediately - mockRaf = vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb: FrameRequestCallback) => { - cb(0); - return 0; - }) as ReturnType; + mockRaf = vi + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb: FrameRequestCallback) => { + cb(0); + return 0; + }) as ReturnType; }); afterAll(() => { diff --git a/packages/hooks/src/useThrottle/index.ts b/packages/hooks/src/useThrottle/index.ts index 07d960d20d..2efdcb6931 100644 --- a/packages/hooks/src/useThrottle/index.ts +++ b/packages/hooks/src/useThrottle/index.ts @@ -1,19 +1,8 @@ -import { useEffect, useState } from 'react'; -import useThrottleFn from '../useThrottleFn'; import type { ThrottleOptions } from './throttleOptions'; +import useThrottleFn from '../useThrottleFn'; +import { createRateLimitValue } from '../createRateLimitHooks'; -function useThrottle(value: T, options?: ThrottleOptions) { - const [throttled, setThrottled] = useState(value); - - const { run } = useThrottleFn(() => { - setThrottled(value); - }, options); - - useEffect(() => { - run(); - }, [value]); - - return throttled; -} +const useThrottle = createRateLimitValue(useThrottleFn); export default useThrottle; +export type { ThrottleOptions }; diff --git a/packages/hooks/src/useThrottleEffect/index.ts b/packages/hooks/src/useThrottleEffect/index.ts index e0e52ba67d..3b59aea126 100644 --- a/packages/hooks/src/useThrottleEffect/index.ts +++ b/packages/hooks/src/useThrottleEffect/index.ts @@ -1,25 +1,8 @@ -import { useEffect, useState } from 'react'; -import type { DependencyList, EffectCallback } from 'react'; import type { ThrottleOptions } from '../useThrottle/throttleOptions'; import useThrottleFn from '../useThrottleFn'; -import useUpdateEffect from '../useUpdateEffect'; +import { createRateLimitEffect } from '../createRateLimitHooks'; -function useThrottleEffect( - effect: EffectCallback, - deps?: DependencyList, - options?: ThrottleOptions, -) { - const [flag, setFlag] = useState({}); - - const { run } = useThrottleFn(() => { - setFlag({}); - }, options); - - useEffect(() => { - return run(); - }, deps); - - useUpdateEffect(effect, [flag]); -} +const useThrottleEffect = createRateLimitEffect(useThrottleFn); export default useThrottleEffect; +export type { ThrottleOptions }; diff --git a/packages/hooks/src/useThrottleFn/index.ts b/packages/hooks/src/useThrottleFn/index.ts index 4575b32cf8..447fd0be19 100644 --- a/packages/hooks/src/useThrottleFn/index.ts +++ b/packages/hooks/src/useThrottleFn/index.ts @@ -1,45 +1,10 @@ import throttle from 'lodash/throttle'; -import { useMemo } from 'react'; -import useLatest from '../useLatest'; import type { ThrottleOptions } from '../useThrottle/throttleOptions'; -import useUnmount from '../useUnmount'; -import { isFunction } from '../utils'; -import isDev from '../utils/isDev'; +import { createRateLimitFn } from '../createRateLimitHooks'; type noop = (...args: any[]) => any; -function useThrottleFn(fn: T, options?: ThrottleOptions) { - if (isDev) { - if (!isFunction(fn)) { - console.error(`useThrottleFn expected parameter is a function, got ${typeof fn}`); - } - } - - const fnRef = useLatest(fn); - - const wait = options?.wait ?? 1000; - - const throttled = useMemo( - () => - throttle( - (...args: Parameters): ReturnType => { - return fnRef.current(...args); - }, - wait, - options, - ), - [], - ); - - useUnmount(() => { - throttled.cancel(); - }); - - return { - run: throttled, - cancel: throttled.cancel, - flush: throttled.flush, - }; -} +const useThrottleFn = createRateLimitFn(throttle, 'useThrottleFn'); export default useThrottleFn; +export type { ThrottleOptions }; From b8f263da3c5e0059778d569ae5d5298a22656f53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:33:44 +0000 Subject: [PATCH 3/5] refactor: improve type safety in rate-limit factory functions Co-authored-by: liuyib <38221479+liuyib@users.noreply.github.com> --- .../createRateLimitHooks/createRateLimitEffect.ts | 7 +++---- .../src/createRateLimitHooks/createRateLimitFn.ts | 15 ++++----------- .../createRateLimitHooks/createRateLimitValue.ts | 7 +++---- packages/hooks/src/createRateLimitHooks/index.ts | 2 +- packages/hooks/src/useDebounceFn/index.ts | 2 +- packages/hooks/src/useThrottleFn/index.ts | 2 +- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts index 93a674488a..6252947579 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts @@ -1,14 +1,13 @@ import { useEffect, useState } from 'react'; import type { DependencyList, EffectCallback } from 'react'; import useUpdateEffect from '../useUpdateEffect'; -import type { RateLimitOptions } from './createRateLimitFn'; type noop = (...args: any[]) => any; -export function createRateLimitEffect( +export function createRateLimitEffect( useRateLimitFn: ( fn: T, - options?: RateLimitOptions, + options?: Options, ) => { run: T; cancel: () => void; @@ -18,7 +17,7 @@ export function createRateLimitEffect( return function useRateLimitEffect( effect: EffectCallback, deps?: DependencyList, - options?: RateLimitOptions, + options?: Options, ) { const [flag, setFlag] = useState({}); diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts index c2ed8f49b8..99824d3e9c 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts @@ -6,28 +6,21 @@ import isDev from '../utils/isDev'; type noop = (...args: any[]) => any; -export interface RateLimitOptions { - wait?: number; - leading?: boolean; - trailing?: boolean; - maxWait?: number; -} - export interface RateLimitFunction { (...args: Parameters): ReturnType; cancel: () => void; flush: () => void; } -export function createRateLimitFn( +export function createRateLimitFn( rateLimitFn: ( func: (...args: Parameters) => ReturnType, wait: number, - options?: RateLimitOptions, + options?: Options, ) => RateLimitFunction, hookName: string, ) { - return function useRateLimitFn(fn: T, options?: RateLimitOptions) { + return function useRateLimitFn(fn: T, options?: Options) { if (isDev) { if (!isFunction(fn)) { console.error(`${hookName} expected parameter is a function, got ${typeof fn}`); @@ -36,7 +29,7 @@ export function createRateLimitFn( const fnRef = useLatest(fn); - const wait = options?.wait ?? 1000; + const wait = (options as any)?.wait ?? 1000; // Note: We intentionally use an empty dependency array here. // The rateLimitFn is created once and captures the latest fn via fnRef.current diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts index be439d2d1d..9c6222f756 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts @@ -1,19 +1,18 @@ import { useEffect, useState } from 'react'; -import type { RateLimitOptions } from './createRateLimitFn'; type noop = (...args: any[]) => any; -export function createRateLimitValue( +export function createRateLimitValue( useRateLimitFn: ( fn: T, - options?: RateLimitOptions, + options?: Options, ) => { run: T; cancel: () => void; flush: () => void; }, ) { - return function useRateLimitValue(value: V, options?: RateLimitOptions) { + return function useRateLimitValue(value: V, options?: Options) { const [rateLimited, setRateLimited] = useState(value); const { run } = useRateLimitFn( diff --git a/packages/hooks/src/createRateLimitHooks/index.ts b/packages/hooks/src/createRateLimitHooks/index.ts index 4cc83b8206..1cf080d452 100644 --- a/packages/hooks/src/createRateLimitHooks/index.ts +++ b/packages/hooks/src/createRateLimitHooks/index.ts @@ -1,4 +1,4 @@ export { createRateLimitFn } from './createRateLimitFn'; -export type { RateLimitOptions, RateLimitFunction } from './createRateLimitFn'; +export type { RateLimitFunction } from './createRateLimitFn'; export { createRateLimitValue } from './createRateLimitValue'; export { createRateLimitEffect } from './createRateLimitEffect'; diff --git a/packages/hooks/src/useDebounceFn/index.ts b/packages/hooks/src/useDebounceFn/index.ts index 393c9f4ec4..9b3a02e219 100644 --- a/packages/hooks/src/useDebounceFn/index.ts +++ b/packages/hooks/src/useDebounceFn/index.ts @@ -4,7 +4,7 @@ import { createRateLimitFn } from '../createRateLimitHooks'; type noop = (...args: any[]) => any; -const useDebounceFn = createRateLimitFn(debounce, 'useDebounceFn'); +const useDebounceFn = createRateLimitFn(debounce, 'useDebounceFn'); export default useDebounceFn; export type { DebounceOptions }; diff --git a/packages/hooks/src/useThrottleFn/index.ts b/packages/hooks/src/useThrottleFn/index.ts index 447fd0be19..2156b92a64 100644 --- a/packages/hooks/src/useThrottleFn/index.ts +++ b/packages/hooks/src/useThrottleFn/index.ts @@ -4,7 +4,7 @@ import { createRateLimitFn } from '../createRateLimitHooks'; type noop = (...args: any[]) => any; -const useThrottleFn = createRateLimitFn(throttle, 'useThrottleFn'); +const useThrottleFn = createRateLimitFn(throttle, 'useThrottleFn'); export default useThrottleFn; export type { ThrottleOptions }; From ae7068849df55e039a5d36c6daf8cd49edc96969 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:37:08 +0000 Subject: [PATCH 4/5] refactor: improve type constraints and add documentation for type assertions Co-authored-by: liuyib <38221479+liuyib@users.noreply.github.com> --- .../src/createRateLimitHooks/createRateLimitEffect.ts | 3 +++ .../hooks/src/createRateLimitHooks/createRateLimitFn.ts | 9 +++++++-- .../src/createRateLimitHooks/createRateLimitValue.ts | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts index 6252947579..aea627540c 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitEffect.ts @@ -21,6 +21,9 @@ export function createRateLimitEffect( ) { const [flag, setFlag] = useState({}); + // Note: The type assertion is safe here because we're creating a zero-argument + // callback that matches the noop signature. The callback will be called without + // arguments by the rate-limiting logic. const { run } = useRateLimitFn( (() => { setFlag({}); diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts index 99824d3e9c..eb969c8a99 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts @@ -12,7 +12,12 @@ export interface RateLimitFunction { flush: () => void; } -export function createRateLimitFn( +// Base constraint for rate limit options to ensure they have a 'wait' property +interface BaseRateLimitOptions { + wait?: number; +} + +export function createRateLimitFn( rateLimitFn: ( func: (...args: Parameters) => ReturnType, wait: number, @@ -29,7 +34,7 @@ export function createRateLimitFn( const fnRef = useLatest(fn); - const wait = (options as any)?.wait ?? 1000; + const wait = options?.wait ?? 1000; // Note: We intentionally use an empty dependency array here. // The rateLimitFn is created once and captures the latest fn via fnRef.current diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts index 9c6222f756..6407ec4314 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitValue.ts @@ -15,6 +15,9 @@ export function createRateLimitValue( return function useRateLimitValue(value: V, options?: Options) { const [rateLimited, setRateLimited] = useState(value); + // Note: The type assertion is safe here because we're creating a zero-argument + // callback that matches the noop signature. The callback will be called without + // arguments by the rate-limiting logic. const { run } = useRateLimitFn( (() => { setRateLimited(value); From 48e0c8aeaa31ec47fa3d7528014182a2f01fe7b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:37:53 +0000 Subject: [PATCH 5/5] refactor: extract duplicated rate-limiting code into factory functions Co-authored-by: liuyib <38221479+liuyib@users.noreply.github.com> --- packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts index eb969c8a99..ea56ff3d18 100644 --- a/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts +++ b/packages/hooks/src/createRateLimitHooks/createRateLimitFn.ts @@ -17,7 +17,10 @@ interface BaseRateLimitOptions { wait?: number; } -export function createRateLimitFn( +export function createRateLimitFn< + T extends noop, + Options extends BaseRateLimitOptions = BaseRateLimitOptions, +>( rateLimitFn: ( func: (...args: Parameters) => ReturnType, wait: number,