<script lang="ts">
import { TanStackDevtools } from '@tanstack/svelte-devtools'
import { pacerDevtoolsPlugin } from '@tanstack/svelte-pacer-devtools'
import { onDestroy } from 'svelte'
import { createAsyncRateLimiter } from '@tanstack/svelte-pacer/async-rate-limiter'
interface SearchResult {
id: number
title: string
}
const fakeApi = async (term: string): Promise<Array<SearchResult>> => {
await new Promise((resolve) => setTimeout(resolve, 300))
return [
{ id: 1, title: `${term} result ${Math.floor(Math.random() * 100)}` },
{ id: 2, title: `${term} result ${Math.floor(Math.random() * 100)}` },
{ id: 3, title: `${term} result ${Math.floor(Math.random() * 100)}` },
]
}
let windowType = $state<'fixed' | 'sliding'>('fixed')
let searchTerm = $state('')
let results = $state<Array<SearchResult>>([])
let error = $state<Error | null>(null)
const handleSearch = async (term: string) => {
if (!term) {
results = []
return
}
const data = await fakeApi(term)
results = data
error = null
}
const setSearchAsyncRateLimiter = createAsyncRateLimiter(
handleSearch,
() => ({
key: 'createAsyncRateLimiter',
windowType: windowType,
limit: 3,
window: 3000,
onReject: (_args, rateLimiter) => {
console.log(
`Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`,
)
},
onError: (cause) => {
console.error('Search failed:', cause)
error = cause as Error
results = []
},
}),
)
const handleSearchRateLimited = setSearchAsyncRateLimiter.maybeExecute
onDestroy(() => {
console.log('unmount')
setSearchAsyncRateLimiter.reset()
})
async function onSearchChange(e: Event) {
const newTerm = (e.target as HTMLInputElement).value
searchTerm = newTerm
await handleSearchRateLimited(newTerm)
}
</script>
<div>
<h1>TanStack Pacer createAsyncRateLimiter Example</h1>
<div style="display: grid; gap: 0.5rem; margin-bottom: 1rem">
<label
><input
type="radio"
name="windowType"
value="fixed"
checked={windowType === 'fixed'}
oninput={() => (windowType = 'fixed')}
/>Fixed Window</label
><label
><input
type="radio"
name="windowType"
value="sliding"
checked={windowType === 'sliding'}
oninput={() => (windowType = 'sliding')}
/>Sliding Window</label
>
</div>
<div>
<input
autofocus
type="search"
value={searchTerm}
oninput={onSearchChange}
placeholder="Type to search..."
style="width: 100%"
autocomplete="new-password"
/>
</div>
{#if error}<div>
Error: {error.message}
</div>{/if}<setSearchAsyncRateLimiter.Subscribe
selector={(state) => ({
successCount: state.successCount,
rejectionCount: state.rejectionCount,
isExecuting: state.isExecuting,
})}
>{#snippet children({ successCount, rejectionCount, isExecuting })}<div>
<table>
<tbody
><tr><td>API calls made:</td><td>{successCount}</td></tr><tr
><td>Rejected calls:</td><td>{rejectionCount}</td></tr
><tr><td>Is executing:</td><td>{isExecuting ? 'Yes' : 'No'}</td></tr
><tr
><td>Results:</td><td
>{#if results.length > 0}<ul>
{#each results as item, index (index)}<li>
{item.title}
</li>{/each}
</ul>{:else}{'No results'}{/if}</td
></tr
></tbody
>
</table>
</div>{/snippet}</setSearchAsyncRateLimiter.Subscribe
><setSearchAsyncRateLimiter.Subscribe selector={(state) => state}
>{#snippet children(state)}<pre style="margin-top: 20px">{JSON.stringify(
state,
null,
2,
)}</pre>{/snippet}</setSearchAsyncRateLimiter.Subscribe
>
</div>
{#if import.meta.env.DEV}<TanStackDevtools
plugins={[pacerDevtoolsPlugin()]}
/>{/if}