@tanstack/db
Version:
A reactive client store for building super fast apps on sync
89 lines (86 loc) • 2.67 kB
text/typescript
import type { ThrottleStrategy, ThrottleStrategyOptions } from './types'
import type { Transaction } from '../transactions'
/**
* Creates a throttle strategy that ensures transactions are evenly spaced
* over time.
*
* Provides smooth, controlled execution patterns ideal for UI updates like
* sliders, progress bars, or scroll handlers where you want consistent
* execution timing.
*
* @param options - Configuration for throttle behavior
* @returns A throttle strategy instance
*
* @example
* ```ts
* // Throttle slider updates to every 200ms
* const mutate = usePacedMutations({
* onMutate: (volume) => {
* settingsCollection.update('volume', draft => { draft.value = volume })
* },
* mutationFn: async ({ transaction }) => {
* await api.updateVolume(transaction.mutations)
* },
* strategy: throttleStrategy({ wait: 200 })
* })
* ```
*
* @example
* ```ts
* // Throttle with leading and trailing execution
* const mutate = usePacedMutations({
* onMutate: (data) => {
* collection.update(id, draft => { Object.assign(draft, data) })
* },
* mutationFn: async ({ transaction }) => {
* await api.save(transaction.mutations)
* },
* strategy: throttleStrategy({
* wait: 500,
* leading: true,
* trailing: true
* })
* })
* ```
*/
export function throttleStrategy(
options: ThrottleStrategyOptions,
): ThrottleStrategy {
const leading =
options.leading === true ||
(options.leading === undefined && options.trailing !== true)
const trailing = options.trailing !== false
let nextAllowedAt = Number.NEGATIVE_INFINITY
let trailingTimeout: ReturnType<typeof setTimeout> | undefined
let pendingCallback: (() => Transaction) | undefined
return {
_type: `throttle`,
options,
execute: <T extends object = Record<string, unknown>>(
fn: () => Transaction<T>,
) => {
const now = Date.now()
if (leading && trailingTimeout === undefined && now >= nextAllowedAt) {
nextAllowedAt = now + options.wait
fn()
return
}
if (!trailing) return false
pendingCallback = fn as () => Transaction
if (trailingTimeout === undefined) {
const delay = leading ? Math.max(0, nextAllowedAt - now) : options.wait
trailingTimeout = setTimeout(() => {
trailingTimeout = undefined
nextAllowedAt = Date.now() + options.wait
const callback = pendingCallback
pendingCallback = undefined
callback?.()
}, delay)
}
return
},
cleanup: () => {
// Pending work keeps its timer until the scheduled callback runs.
},
}
}