On this page

Stores API (Bun)

Using Node.js? See the Node.js Stores API for @joint-ops/hitlimit.

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:

types.ts
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

memory.ts
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

sqlite.ts
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

redis.ts
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).
keyPrefix string 'hitlimit:' Prefix prepended to all Valkey keys.

Usage

valkey.ts
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).
keyPrefix string 'hitlimit:' Prefix prepended to all DragonflyDB keys.

Usage

dragonfly.ts
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
url string Connection string. Store creates and owns a Bun SQL client. Closed on shutdown().
client SQL Caller-owned Bun SQL instance. Store uses it but does not close it.
pool Pool Deprecated. Legacy pg.Pool. Caller-owned, store does not close it.
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

postgres.ts
import { hitlimit } from '@joint-ops/hitlimit-bun'
import { postgresStore } from '@joint-ops/hitlimit-bun/stores/postgres'

const limiter = hitlimit({
  limit: 100,
  window: '1m',
  store: postgresStore({ url: 'postgres://localhost:5432/mydb' })
})

Custom Stores

Implement the HitLimitStore interface to create a custom storage backend. At minimum, provide hit() and reset() methods.

custom-store.ts
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
MongoDB Network-bound Network-bound Yes Yes No
MySQL 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.