On this page
  1. Install
  2. Install and define policy
  3. Definition properties
  4. Preserve fixed-window behavior
  5. Rolling expiry and denial cooldown
  6. Limiter configuration and consumption
  7. Storage bridge
  8. Boundary
  9. API entry points and requirements
  10. Peer dependencies

@playstack/rate-limit

Workers-compatible token buckets, private subject keys, and an atomic multi-bucket storage contract.

Free. MIT licensed. Check preview availability before installing. See package access.

@playstack/rate-limit evaluates Workers-compatible token-bucket, fixed-window or rolling-window/cooldown policies with private subject keys and atomic storage.

Install

After confirming preview access, install the package at your application's shared Playstack version:

sh
npm install --save-exact @playstack/rate-limit@0.1.0-beta.1

Check the peer requirements below before choosing a runtime or provider.

Install and define policy

ts
import { HmacSha256KeyHasher, createRateLimiter, defineRateLimits } from '@playstack/rate-limit'

const definitions = defineRateLimits({
  'api.request': {
    failureMode: 'closed',
    buckets: [
      { subject: 'account', capacity: 1_000, refill: { tokens: 1_000, everyMs: 60_000 } },
      { subject: 'apiKey', capacity: 100, refill: { tokens: 100, everyMs: 60_000 } },
    ],
  },
})

export const limiter = createRateLimiter({
  definitions,
  environment: 'production',
  store,
  clock,
  keyHasher: new HmacSha256KeyHasher(crypto.subtle, rateLimitKey),
  onEvent: (event) => operations.record(event),
})

const decision = await limiter.consume('api.request', {
  subjects: { account: accountId, apiKey: key.id },
})

Raw subject values are HMAC-protected before storage. Use a deployment secret of at least 32 bytes and plan its rotation as a deliberate bucket reset.

Definition properties

PropertyMeaning
Definition nameLowercase dot notation such as api.request.
failureModeExplicit closed or open behavior when storage is unavailable.
bucketsOne or more unique subject policies evaluated atomically.
subjectaccount, accountMember, apiKey, custom, ip, or user.
capacityMaximum burst capacity in tokens.
refill.tokens / everyMsPositive refill amount and interval.
costOptional default operation cost; defaults to one token.

Values compile to integer microtokens, allowing fractional token policies without floating-point storage drift.

Preserve fixed-window behavior

Applications deliberately selecting epoch-aligned fixed windows can use defineFixedWindowRateLimits() and createFixedWindowRateLimiter() from @playstack/rate-limit/fixed-window. This is not equivalent to every existing throttler or cooldown policy.

Fixed windows align to epoch boundaries, block until the next boundary after the limit is spent, and avoid partial spending across multi-subject policies. Choosing the strategy in composition keeps the behavior visible; it is not a hidden option that changes token-bucket semantics.

Rolling expiry and denial cooldown

ts
import {
  createRollingWindowRateLimiter,
  defineRollingWindowRateLimits,
} from '@playstack/rate-limit/rolling-window'

const limiter = createRollingWindowRateLimiter({
  definitions: defineRollingWindowRateLimits({
    'api.request': {
      failureMode: 'closed',
      windows: [{ subject: 'ip', limit: 10, windowMs: 60_000, cooldownMs: 60_000 }],
    },
  }),
  environment: 'production', store, clock, keyHasher,
})

Each accepted hit expires individually. First refusal starts the full cooldown; blocked attempts neither spend quota nor extend cooldown. Cooldown expiry starts empty. All subjects are evaluated atomically: denial spends no peer quota and only exhausted subjects enter cooldown. Limits are bounded at 10,000 per subject.

Use the PostgreSQL PrismaRollingWindowRateLimitStore for distributed enforcement; the memory store is for tests/local single-process use. Rolling Redis and Durable Object adapters are not supplied. Existing token-bucket/fixed-window defaults are unchanged. Storage failure remains unavailable under closed policy, not a 429; fail-open is an explicit bypass. Do not retry an ambiguous commit.

The host owns proxy/subject policy, health exemptions, retry headers and a reviewed counter cutover. New psrl:rw:v1: keys cannot silently share old counters without changing quota. PostgreSQL setup describes server-clock authority and bounded cleanup.

Limiter configuration and consumption

RateLimiterOptions propertyRequiredPurpose
definitionsYesValidated named policies.
environmentYesLowercase identifier included in storage-key isolation.
storeYesAtomic multi-bucket bridge.
clockYesApplication time for stores that do not provide server time.
keyHasherYesOne-way subject-key derivation.
onEventNoIsolated observer for denied or fail-open-bypassed operations.

consume supplies required subject IDs, optional operation cost multiplier, and optional per-subject capacity/refill/cost overrides. Every subject declared by the policy must be resolved before calling it.

Storage bridge

ts
interface RateLimitStore {
  name: string
  consume(input: { nowMs: number; buckets: readonly CompiledBucket[] }): Promise<RateLimitDecision>
}

The store must decide and update every bucket atomically. A denied group spends none of them. The decision returns overall and per-bucket limit, remaining capacity, and optional retry time.

Use @playstack/rate-limit/testing only for tests and local single-process development. Select Redis, Prisma, or Durable Objects at a distributed deployment boundary.

Boundary

Rate limiting is not a billing meter, entitlement engine, concurrency semaphore, or CDN defense. Resolve authenticated caller and tenant scope first; use edge/CDN defenses independently.

API entry points and requirements

Reference snapshot: @playstack/rate-limit@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

Public entry pointDeclaration file
@playstack/rate-limit/rolling-window./dist/rolling-window.d.ts
@playstack/rate-limit./dist/index.d.ts
@playstack/rate-limit/errors./dist/errors.d.ts
@playstack/rate-limit/fixed-window./dist/fixed-window.d.ts
@playstack/rate-limit/algorithm./dist/algorithm.d.ts
@playstack/rate-limit/definitions./dist/definitions.d.ts
@playstack/rate-limit/types./dist/types.d.ts
@playstack/rate-limit/testing./dist/testing.d.ts
@playstack/rate-limit/package.jsonNo TypeScript declaration (asset or metadata export).

Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.

Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

Go

Playstack Pro tag
OriginsPricingBlogNewsletterChangelogStatusRoadmap
ContributorsCommunityIn Use ShowcaseCase StudiesPartnersSponsors
FAQsSupportContact

© 2026 Playstack. All rights reserved.

With OSS
Terms of ServicePrivacy PolicyCookie PolicyImprint

By

Commune Software