npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bantai-dev/with-rate-limit

v1.2.0

Published

Rate limiting extension for @bantai-dev/core

Readme

@bantai-dev/with-rate-limit

Rate limiting extension for @bantai-dev/core

Add rate limiting capabilities to your Bantai contexts with support for multiple rate limiting strategies including fixed-window, sliding-window, and token-bucket algorithms.

Website: https://bantai.vercel.app/

Installation

npm install @bantai-dev/with-rate-limit @bantai-dev/core @bantai-dev/with-storage zod
# or
pnpm add @bantai-dev/with-rate-limit @bantai-dev/core @bantai-dev/with-storage zod
# or
yarn add @bantai-dev/with-rate-limit @bantai-dev/core @bantai-dev/with-storage zod

Note: @bantai-dev/core, @bantai-dev/with-storage, and zod are peer dependencies and must be installed separately.

Quick Start

import { z } from 'zod';
import { defineContext, definePolicy, evaluatePolicy, allow } from '@bantai-dev/core';
import { createMemoryStorage } from '@bantai-dev/with-storage';
import {
  withRateLimit,
  defineRateLimitRule,
  rateLimit,
} from '@bantai-dev/with-rate-limit';

// 1. Define your base context
const apiContext = defineContext(
  z.object({
    userId: z.string(),
    endpoint: z.string(),
  })
);

// 2. Extend context with rate limiting
// generateKey will automatically create keys from your input
const rateLimitedContext = withRateLimit(apiContext, {
  storage: createMemoryStorage(rateLimit.storageSchema),
  generateKey: (input) => `api:${input.userId}:${input.endpoint}`,
  defaultValues: {
    rateLimit: {
      type: 'fixed-window',
      limit: 100,
      period: '1h',
    },
  },
});

// 3. Define a rate limiting rule using defineRateLimitRule
// This automatically handles rate limit checking and incrementing
const rateLimitRule = defineRateLimitRule(
  rateLimitedContext,
  'check-rate-limit',
  async (input) => {
    // Your business logic here
    // Rate limit is already checked and will be incremented on allow
    // input.currentLimit contains the rate limit check result
    console.log(`Remaining: ${input.currentLimit.remaining}`);
    return allow({ reason: 'Request allowed' });
  },
  {
    config: {
      limit: 100,
      period: '1h',
      type: 'fixed-window',
    },
  }
);

// 4. Define policy
const apiPolicy = definePolicy(
  rateLimitedContext,
  'api-rate-limit-policy',
  [rateLimitRule],
  {
    defaultStrategy: 'preemptive',
  }
);

// 5. Evaluate policy
// The generateKey function will create the key automatically
const result = await evaluatePolicy(apiPolicy, {
  userId: 'user123',
  endpoint: '/api/search',
});

Rate Limiting Strategies

Fixed Window

Fixed window rate limiting divides time into discrete windows. All requests within a window count toward the limit, and the counter resets at the start of each new window.

Use cases: Simple rate limiting, API quotas, basic throttling

{
  type: 'fixed-window',
  key: 'user:123',
  limit: 100,
  period: '1h', // Supports ms format: '1h', '30m', '5s', etc.
}

Sliding Window

Sliding window rate limiting tracks individual request timestamps. Only requests within the current window count toward the limit, providing smoother rate limiting.

Use cases: More accurate rate limiting, preventing burst traffic

{
  type: 'sliding-window',
  key: 'user:123',
  limit: 100,
  period: '1h',
}

Token Bucket

Token bucket rate limiting uses a bucket that refills at a constant rate. Requests consume tokens, and requests are allowed when tokens are available.

Use cases: Burst handling, smooth rate limiting with refill

{
  type: 'token-bucket',
  key: 'user:123',
  limit: 10000,
  period: '1d', // Refills to full capacity (10k tokens) over 1 day
  cost: 1, // Optional: tokens consumed per request (default: 1)
}

Note: The period is a time period (e.g. "1d", "1h", "30m") representing the time to refill from empty to full capacity. The refill rate is automatically calculated as limit / period. For example, limit: 10_000 and period: '1d' means the bucket can hold 10,000 tokens and refills at a rate of 10,000 tokens per day.

Cost: The cost parameter (optional, default: 1) specifies how many tokens/requests each operation consumes. This allows you to implement variable-cost rate limiting where different operations consume different amounts. For example, a simple API call might cost 1, while a complex operation might cost 5. This parameter works for all rate limiting strategies.

API Reference

withRateLimit(context, options?)

Extends a Bantai context with rate limiting capabilities. Adds rateLimit schema fields and tools to the context.

Parameters:

  • context: A Bantai context definition
  • options:
    • storage?: A storage adapter implementing RateLimitStorage interface
    • defaultValues?: Default values for rate limit configuration
    • generateKey?: Optional function to generate rate limit keys dynamically from context input. If provided, this function will be used when rateLimit.key is not specified in the input.

Returns: Extended context with rate limiting capabilities

Example with generateKey:

import { createMemoryStorage } from '@bantai-dev/with-storage';
import { withRateLimit, rateLimit } from '@bantai-dev/with-rate-limit';

const rateLimitedContext = withRateLimit(apiContext, {
  storage: createMemoryStorage(rateLimit.storageSchema),
  generateKey: (input) => `api:${input.userId}:${input.endpoint}`,
  defaultValues: {
    rateLimit: {
      type: 'fixed-window',
      limit: 100,
      period: '1h',
    },
  },
});

When using defineRateLimitRule, if rateLimit.key is not provided in the input, the generateKey function will be used automatically.

defineRateLimitRule(context, name, evaluate, options)

A helper function that automatically handles rate limit checking and incrementing. This simplifies rule creation by handling the rate limit logic for you.

Parameters:

  • context: A context extended with rate limiting capabilities via withRateLimit
  • name: Unique name for the rule
  • evaluate: Your rule evaluation function. The function receives:
    • input: The context input with an additional currentLimit property containing the rate limit check result
    • context: The evaluation context with tools
  • options: Configuration object
    • config: Rate limit configuration (required). This will be merged with any rateLimit config from the input.
    • onAllow?: Optional hook called after rate limit is incremented (if your rule allows)
    • onDeny?: Optional hook called if your rule denies

Returns: RuleDefinition<TContext, TName>

How it works:

  1. Checks the rate limit before evaluating your rule
  2. If rate limit is exceeded, returns deny immediately
  3. If rate limit passes, evaluates your rule with currentLimit available in the input
  4. On allow, automatically increments the rate limit counter
  5. Calls your optional hooks

Key resolution order:

  1. If rateLimit.key is provided in the input, use it
  2. Otherwise, if generateKey function is available, use it
  3. Otherwise, fall back to 'unknown-key'

Example:

const rateLimitRule = defineRateLimitRule(
  rateLimitedContext,
  'api-rule',
  async (input) => {
    // input.currentLimit contains the rate limit check result
    console.log(`Remaining: ${input.currentLimit.remaining}`);
    
    // Your business logic here
    return allow({ reason: 'Request processed' });
  },
  {
    config: {
      limit: 100,
      period: '1h',
      type: 'fixed-window',
    },
    onAllow: async (result, input) => {
      // Optional: Additional logic after rate limit increment
      console.log(`Request allowed for ${input.userId}`);
    },
  }
);

rateLimit.checkRateLimit(storage, config, clock?)

Checks if a rate limit would be exceeded without incrementing the counter. Use this in your rule's evaluate function when not using defineRateLimitRule.

Parameters:

  • storage: Storage adapter implementing RateLimitStorage
  • config: Rate limit configuration object
  • clock?: Optional clock function for testing (defaults to Date.now)

Returns: Promise<RateLimitCheckResult>

RateLimitCheckResult:

{
  allowed: boolean;
  remaining: number;
  resetAt: number; // Unix timestamp in milliseconds
  reason?: string;
}

rateLimit.incrementRateLimit(storage, config, clock?)

Increments the rate limit counter. Use this in your rule's onAllow hook when not using defineRateLimitRule.

Parameters:

  • storage: Storage adapter implementing RateLimitStorage
  • config: Rate limit configuration object
  • clock?: Optional clock function for testing (defaults to Date.now)

Returns: Promise<void>

Storage Integration

The rate limiting extension requires a storage adapter. You can use:

  • Memory storage (development/testing): createMemoryStorage from this package
  • Redis storage (production): createRedisStorage from @bantai-dev/storage-redis
  • Custom storage: Implement the StorageAdapter interface from @bantai-dev/with-storage

Using Redis Storage

import { createRedisStorage } from '@bantai-dev/storage-redis';
import { rateLimit } from '@bantai-dev/with-rate-limit';

const redisStorage = createRedisStorage(
  { url: process.env.REDIS_URL },
  rateLimit.storageSchema
);

const rateLimitedContext = withRateLimit(apiContext, {
  storage: redisStorage,
});

Examples

Per-User Rate Limiting

import { defineRule, allow, deny } from '@bantai-dev/core';

const userRateLimitRule = defineRule(
  rateLimitedContext,
  'user-rate-limit',
  async (input, { tools }) => {
    const result = await tools.rateLimit.checkRateLimit(
      tools.storage,
      {
        key: `user:${input.userId}`,
        type: 'fixed-window',
        limit: 1000,
        period: '24h',
      }
    );

    return result.allowed ? allow({ reason: 'Rate limit OK' }) : deny({ reason: result.reason });
  },
  {
    onAllow: async (result, input, { tools }) => {
      await tools.rateLimit.incrementRateLimit(
        tools.storage,
        {
          key: `user:${input.userId}`,
          type: 'fixed-window',
          limit: 1000,
          period: '24h',
        }
      );
    },
  }
);

Endpoint-Specific Rate Limits

import { defineRule, allow, deny } from '@bantai-dev/core';

const endpointLimits = {
  '/api/auth/login': { limit: 5, period: '15m' },
  '/api/payment': { limit: 10, period: '1m' },
  '/api/search': { limit: 100, period: '1m' },
};

const endpointRateLimitRule = defineRule(
  rateLimitedContext,
  'endpoint-rate-limit',
  async (input, { tools }) => {
    const config = endpointLimits[input.endpoint] || { limit: 50, period: '1h' };
    
    const result = await tools.rateLimit.checkRateLimit(
      tools.storage,
      {
        key: `endpoint:${input.endpoint}:${input.userId}`,
        type: 'sliding-window',
        limit: config.limit,
        period: config.period,
      }
    );

    return result.allowed ? allow({ reason: 'Rate limit OK' }) : deny({ reason: result.reason });
  },
  {
    onAllow: async (result, input, { tools }) => {
      const config = endpointLimits[input.endpoint] || { limit: 50, period: '1h' };
      
      await tools.rateLimit.incrementRateLimit(
        tools.storage,
        {
          key: `endpoint:${input.endpoint}:${input.userId}`,
          type: 'sliding-window',
          limit: config.limit,
          period: config.period,
        }
      );
    },
  }
);

Tier-Based Rate Limiting

import { defineRule, allow, deny } from '@bantai-dev/core';

const tierLimits = {
  free: { limit: 100, period: '1h' },
  premium: { limit: 1000, period: '1h' },
  enterprise: { limit: 10000, period: '1h' },
};

const tierRateLimitRule = defineRule(
  rateLimitedContext,
  'tier-rate-limit',
  async (input, { tools }) => {
    const config = tierLimits[input.userTier];
    
    const result = await tools.rateLimit.checkRateLimit(
      tools.storage,
      {
        key: `tier:${input.userTier}:${input.userId}`,
        type: 'token-bucket',
        limit: config.limit,
        period: '1h', // Refills to full capacity over 1 hour
      }
    );

    return result.allowed ? allow({ reason: 'Rate limit OK' }) : deny({ reason: result.reason });
  },
  {
    onAllow: async (result, input, { tools }) => {
      const config = tierLimits[input.userTier];
      
      await tools.rateLimit.incrementRateLimit(
        tools.storage,
        {
          key: `tier:${input.userTier}:${input.userId}`,
          type: 'token-bucket',
          limit: config.limit,
          period: '1h', // Refills to full capacity over 1 hour
        }
      );
    },
  }
);

Type Safety

The package provides full TypeScript type safety:

  • Context extension: Type-safe context merging with rate limit fields
  • Config validation: Zod schemas validate rate limit configurations
  • Storage types: Type-safe storage adapter interface
  • Result types: Typed rate limit check results

Requirements

  • Node.js >= 20.9.0
  • TypeScript >= 5.0
  • Zod >= 4.3.5
  • @bantai-dev/core
  • @bantai-dev/with-storage

Links

License

MIT