Stores API (Bun)
Stores manage rate limit data persistence in hitlimit-bun. Every store implements
the HitLimitStore interface. Synchronous stores (memory, SQLite) set
isSync = true so the limiter can skip await on the hot path
for maximum throughput.
HitLimitStore Interface
All stores implement this interface from @joint-ops/hitlimit-types:
interface HitLimitStore {
/** If true, hit() returns StoreResult synchronously (not a Promise) */
isSync?: boolean
/** Increment the counter for a key. Returns count and reset timestamp. */
hit(key: string, windowMs: number, limit: number): Promise<StoreResult> | StoreResult
/** Reset all rate limit data for a key. */
reset(key: string): Promise<void> | void
/** Cleanup resources (close connections, clear timers). */
shutdown?(): Promise<void> | void
/** Check if a key is currently banned. */
isBanned?(key: string): Promise<boolean> | boolean
/** Ban a key for the given duration in milliseconds. */
ban?(key: string, durationMs: number): Promise<void> | void
/** Record a violation and return the current violation count. */
recordViolation?(key: string, windowMs: number): Promise<number> | number
/** Atomic hit + ban check in a single round-trip (Redis Lua). */
hitWithBan?(key: string, windowMs: number, limit: number,
banThreshold: number, banDurationMs: number): Promise<HitWithBanResult>
} StoreResult
interface StoreResult {
count: number // Current request count in this window
resetAt: number // Timestamp (ms) when the window resets
} memoryStore()
In-memory storage using a Map. Default store since v1.1.0.
Synchronous hot path (isSync = true) for maximum throughput.
Expired entries are swept every 10 seconds.
Signature
function memoryStore(): HitLimitStore No options. Returns a HitLimitStore instance.
Import
import { memoryStore } from '@joint-ops/hitlimit-bun' Usage
import { hitlimit, memoryStore } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit({
limit: 100,
window: '1m',
store: memoryStore() // Default — can be omitted
}) sqliteStore()
Persistent storage using Bun's native bun:sqlite.
Synchronous hot path (isSync = true) with prepared statements for
optimal query performance. Expired entries are cleaned every 60 seconds.
Signature
function sqliteStore(options?: SqliteStoreOptions): HitLimitStore Import
import { sqliteStore } from '@joint-ops/hitlimit-bun/stores/sqlite' Options
| Option | Type | Default | Description |
|---|---|---|---|
path | string | ':memory:' | Path to the SQLite database file. Use ':memory:' for in-memory storage. |
Usage
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { sqliteStore } from '@joint-ops/hitlimit-bun/stores/sqlite'
const limiter = hitlimit({
limit: 100,
window: '1m',
store: sqliteStore({
path: './rate-limits.db'
})
}) redisStore()
Redis-backed storage for distributed deployments. Uses Lua scripts for atomic
operations (single round-trip per request). Supports atomic
hitWithBan() for combined rate limiting and ban tracking in one call.
Signature
function redisStore(options?: RedisStoreOptions): HitLimitStore Import
import { redisStore } from '@joint-ops/hitlimit-bun/stores/redis' Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | 'redis://localhost:6379' | Redis connection URL. |
keyPrefix | string | 'hitlimit:' | Prefix prepended to all Redis keys. |
Usage
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { redisStore } from '@joint-ops/hitlimit-bun/stores/redis'
const limiter = hitlimit({
limit: 100,
window: '1m',
store: redisStore({
url: 'redis://localhost:6379',
keyPrefix: 'rl:'
})
}) valkeyStore()
Valkey-backed storage for distributed deployments. Thin wrapper over RedisStore —
uses the same ioredis client and atomic Lua scripts. Valkey is the Linux Foundation's
open-source fork of Redis (BSD-3-Clause).
Signature
function valkeyStore(options?: ValkeyStoreOptions): HitLimitStore Import
import { valkeyStore } from '@joint-ops/hitlimit-bun/stores/valkey' Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | — | Valkey connection URL (uses redis:// scheme). |
client | Redis | — | Existing ioredis client instance. |
prefix | string | 'hitlimit:' | Key prefix for rate limit entries. |
Usage
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { valkeyStore } from '@joint-ops/hitlimit-bun/stores/valkey'
const limiter = hitlimit({
limit: 100,
window: '1m',
store: valkeyStore({
url: 'redis://localhost:6379'
})
}) dragonflyStore()
DragonflyDB-backed storage for high-throughput distributed deployments. Thin wrapper over
RedisStore — uses the same ioredis client and Lua scripts. Benefits from
DragonflyDB's parallel Lua execution across different keys.
Signature
function dragonflyStore(options?: DragonflyStoreOptions): HitLimitStore Import
import { dragonflyStore } from '@joint-ops/hitlimit-bun/stores/dragonfly' Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | — | DragonflyDB connection URL (uses redis:// scheme). |
client | Redis | — | Existing ioredis client instance. |
prefix | string | 'hitlimit:' | Key prefix for rate limit entries. |
Usage
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { dragonflyStore } from '@joint-ops/hitlimit-bun/stores/dragonfly'
const limiter = hitlimit({
limit: 100,
window: '1m',
store: dragonflyStore({
url: 'redis://localhost:6379'
})
}) postgresStore()
PostgreSQL-backed storage for distributed deployments. Uses named prepared statements for query plan caching and atomic upserts for race-condition-free increments.
Signature
function postgresStore(options: PostgresStoreOptions): HitLimitStore Import
import { postgresStore } from '@joint-ops/hitlimit-bun/stores/postgres' Options
| Option | Type | Default | Description |
|---|---|---|---|
pool | Pool | — | A pg.Pool instance (required). |
tablePrefix | string | 'hitlimit' | Prefix for rate limit table names. |
cleanupInterval | number | 60000 | Interval (ms) to clean expired entries. |
skipTableCreation | boolean | false | Skip automatic table creation on startup. |
Usage
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { postgresStore } from '@joint-ops/hitlimit-bun/stores/postgres'
import { Pool } from 'pg'
const pool = new Pool({ connectionString: 'postgres://localhost:5432/mydb' })
const limiter = hitlimit({
limit: 100,
window: '1m',
store: postgresStore({ pool })
}) Custom Stores
Implement the HitLimitStore interface to create a custom storage backend.
At minimum, provide hit() and reset() methods.
import type { HitLimitStore, StoreResult } from '@joint-ops/hitlimit-types'
class MyStore implements HitLimitStore {
isSync = true as const
private data = new Map<string, { count: number; resetAt: number }>()
hit(key: string, windowMs: number, _limit: number): StoreResult {
const now = Date.now()
const entry = this.data.get(key)
if (entry && now < entry.resetAt) {
entry.count++
return { count: entry.count, resetAt: entry.resetAt }
}
const resetAt = now + windowMs
this.data.set(key, { count: 1, resetAt })
return { count: 1, resetAt }
}
reset(key: string): void {
this.data.delete(key)
}
shutdown(): void {
this.data.clear()
}
}
Set isSync = true if your store's hit() method returns
a StoreResult directly (not a Promise). This lets the
limiter skip await on the hot path for better performance.
Store Comparison
| Store | Throughput | Latency | Persistent | Distributed | Sync |
|---|---|---|---|---|---|
| Memory | 7.73M ops/sec | ~129ns | No | No | Yes |
| SQLite | 372K ops/sec | ~2.7μs | Yes | No | Yes |
| Redis | Network-bound | Network-bound | Yes | Yes | No |
| Valkey | Network-bound | Network-bound | Yes | Yes | No |
| DragonflyDB | Network-bound | Network-bound | Yes | Yes | No |
| Postgres | Network-bound | Network-bound | Yes | Yes | No |
Memory and SQLite stores run synchronously on Bun's main thread, avoiding Promise overhead entirely. Redis and Postgres require network I/O, so they return Promises. Choose based on whether you need persistence or distribution.