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).
client Redis Existing ioredis client instance.
prefix string 'hitlimit:' Key prefix for rate limit entries.

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).
client Redis Existing ioredis client instance.
prefix string 'hitlimit:' Key prefix for rate limit entries.

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
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

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

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

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.