agentsclimarketplace

Rate limiting persistent

Skill JimmyBlanquet/project-forge/skills/core/rate-limiting-persistent

Persistent rate limiting using PostgreSQL for atomic counters. Survives serverless cold starts and deployments with automatic in-memory fallback.From its SKILL.md

Install
npx -y skills add JimmyBlanquet/project-forge --skill rate-limiting-persistent

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.4 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

Rate Limiting Persistent Skill

Version: 1.0.0 Category: Core Extracted from: Production-tested in a real-world SaaS Production-ready: βœ… Yes

Description

Persistent rate limiting using PostgreSQL for atomic counters. Survives serverless cold starts and deployments with automatic in-memory fallback.

Key Features:

  • πŸ—„οΈ Persistent Storage: Uses PostgreSQL for atomic rate limit tracking
  • ⚑ Serverless-Ready: Survives Vercel/Railway cold starts
  • πŸ›‘οΈ Automatic Fallback: In-memory limiter if database unavailable
  • βš™οΈ Flexible Configuration: Per-action rate limits
  • πŸ”’ Thread-Safe: Atomic PostgreSQL function prevents race conditions
  • 🧹 Auto-Cleanup: Periodic cleanup of expired entries

What This Skill Provides

Core Components

  1. checkRateLimit() - Main rate limiting function

    • Uses PostgreSQL increment_rate_limit() function
    • Automatic fallback to in-memory limiter
    • Returns: { allowed, remaining, resetAt }
  2. PostgreSQL Function - Atomic counter increment

    • UPSERT with ON CONFLICT
    • Automatic window reset when expired
    • Returns allowed status + remaining count
  3. In-Memory Fallback - Resilience when DB unavailable

    • Map-based storage
    • Automatic cleanup every 5 minutes
    • Same interface as persistent limiter

Installation

1. Run Installation Script

cd skills/core/rate-limiting-persistent
bash install.sh

2. Apply Supabase Migration

In your Supabase dashboard or CLI:

supabase migration new create_rate_limits
# Copy contents from supabase/migrations/20251223100001_create_rate_limits.sql
supabase db push

Or manually:

-- See supabase/migrations/20251223100001_create_rate_limits.sql
CREATE TABLE rate_limits (...);
CREATE FUNCTION increment_rate_limit(...);

3. Install Dependencies

Already included if you have Supabase:

npm install @supabase/supabase-js

Usage

Basic Usage

import { checkRateLimit } from '@/lib/rate-limit/persistent-limiter'

// In your API route
export async function POST(request: Request) {
  const { userId } = await getUser()

  const rateLimit = await checkRateLimit(userId, {
    action: 'api_call',
    maxRequests: 60,
    windowSeconds: 60
  })

  if (!rateLimit.allowed) {
    return new Response('Rate limit exceeded', {
      status: 429,
      headers: {
        'X-RateLimit-Remaining': '0',
        'X-RateLimit-Reset': new Date(rateLimit.resetAt).toISOString()
      }
    })
  }

  // Process request...
  return Response.json({ success: true })
}

With Rate Limit Headers

async function handleRateLimitedRequest(userId: string) {
  const rateLimit = await checkRateLimit(userId, {
    action: 'api',
    maxRequests: 100,
    windowSeconds: 60
  })

  const headers = new Headers({
    'X-RateLimit-Limit': '100',
    'X-RateLimit-Remaining': String(rateLimit.remaining),
    'X-RateLimit-Reset': String(rateLimit.resetAt)
  })

  if (!rateLimit.allowed) {
    return new Response('Too many requests', {
      status: 429,
      headers
    })
  }

  // Process request...
  const response = await fetch(...)

  // Add rate limit headers to successful response
  headers.forEach((value, key) => {
    response.headers.set(key, value)
  })

  return response
}

Pre-Configured Rate Limiters

Create helper functions for common use cases:

// In your project
export async function checkApiRateLimit(userId: string) {
  return checkRateLimit(userId, {
    action: 'api',
    maxRequests: 60,
    windowSeconds: 60
  })
}

export async function checkAIRateLimit(userId: string) {
  return checkRateLimit(userId, {
    action: 'ai_generation',
    maxRequests: 10,   // Expensive operation
    windowSeconds: 60
  })
}

export async function checkReadRateLimit(userId: string) {
  return checkRateLimit(userId, {
    action: 'read',
    maxRequests: 100,  // Higher for reads
    windowSeconds: 60
  })
}

Per-IP Rate Limiting

import { checkRateLimit } from '@/lib/rate-limit/persistent-limiter'

export async function POST(request: Request) {
  const ip = request.headers.get('x-forwarded-for') || 'unknown'

  const rateLimit = await checkRateLimit(ip, {
    action: 'login_attempt',
    maxRequests: 5,
    windowSeconds: 300  // 5 minutes
  })

  if (!rateLimit.allowed) {
    return new Response('Too many login attempts', { status: 429 })
  }

  // Process login...
}

Configuration

Rate Limit Strategies

Choose limits based on operation cost:

Operation TypeRequests/MinRationale
AI Generation10Very expensive ($0.01-$0.10 per request)
Database Writes30Moderate cost, prevent abuse
API Reads60Standard API rate limit
Static Assets100+Cheap, allow high throughput

Window Strategies

// Short window (burst protection)
{
  maxRequests: 10,
  windowSeconds: 10  // 10 requests per 10 seconds
}

// Standard window (minute-based)
{
  maxRequests: 60,
  windowSeconds: 60  // 60 requests per minute
}

// Long window (hourly quotas)
{
  maxRequests: 1000,
  windowSeconds: 3600  // 1000 requests per hour
}

Architecture

How It Works

  1. Client calls checkRateLimit()

    checkRateLimit(userId, { action, maxRequests, windowSeconds })
    
  2. PostgreSQL atomic function

    INSERT INTO rate_limits (key, count, window_end)
    VALUES (...)
    ON CONFLICT (key) DO UPDATE
    SET count = CASE WHEN expired THEN 1 ELSE count + 1 END
    RETURNING allowed, remaining, reset_at
    
  3. Response with headers

    {
      allowed: true,
      remaining: 58,
      resetAt: 1705500000000
    }
    

Fallback Mechanism

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  checkRateLimit(userId, config) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Try PostgreSQL β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
      β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
      β”‚             β”‚
   Success      Failure
      β”‚             β”‚
      β–Ό             β–Ό
  Return     In-Memory
  Result     Fallback

Testing

cd skills/core/rate-limiting-persistent
npm test

Database Schema

CREATE TABLE rate_limits (
    id UUID PRIMARY KEY,
    key TEXT UNIQUE NOT NULL,  -- "action:userId"
    count INTEGER NOT NULL,
    window_start TIMESTAMPTZ NOT NULL,
    window_end TIMESTAMPTZ NOT NULL
);

CREATE FUNCTION increment_rate_limit(
    p_key TEXT,
    p_max_requests INTEGER,
    p_window_seconds INTEGER
) RETURNS TABLE (allowed BOOLEAN, remaining INTEGER, reset_at TIMESTAMPTZ);

Maintenance

Cleanup Expired Entries

Run periodically via Supabase CRON or GitHub Actions:

SELECT cleanup_expired_rate_limits();
-- Returns count of deleted rows

Monitoring

-- Active rate limits
SELECT key, count, window_end
FROM rate_limits
WHERE window_end > NOW()
ORDER BY count DESC
LIMIT 20;

-- Users hitting limits
SELECT key, count
FROM rate_limits
WHERE count >= (/* your max */)
AND window_end > NOW();

Performance

Benchmarks (from internal SaaS production):

  • Latency: ~5-10ms per check (PostgreSQL)
  • Fallback latency: <1ms (in-memory)
  • Throughput: 10,000+ checks/sec
  • Database: Minimal storage (~100 bytes per active limit)

Troubleshooting

"No data returned from increment_rate_limit"

Ensure the PostgreSQL function is created:

supabase db push
# Or manually create function

High latency

Enable connection pooling in Supabase:

const supabase = createClient(url, key, {
  db: { schema: 'public' },
  auth: { persistSession: false }
})

Fallback always triggered

Check Supabase connection:

const { data, error } = await supabase.from('rate_limits').select('count()').limit(1)
if (error) console.error('Supabase connection issue:', error)

License

MIT


Extracted from: a previous internal SaaS (production-grade, 67K lines) Last updated: 2026-01-17

What ships with it: 5 files

16.3 KB alongside SKILL.md, 3 of them executable

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.