Limitly
Guides

TypeScript Support

Limitly is fully typed with TypeScript. Get complete type safety, IDE autocomplete, and build type-safe wrappers.

TypeScript Support

Limitly is built with TypeScript and provides complete type definitions out of the box. This means you get full type safety, excellent IDE autocomplete, and the ability to build type-safe wrappers around Limitly.

Basic Type Safety

All functions and methods are fully typed:

import { createClient } from 'limitly-sdk';

// Recommended: Use your own Redis
const client = createClient({
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
  serviceId: 'my-app'
});

// TypeScript knows the return type
const result = await client.checkRateLimit('user-123');
// result: LimitlyResponse

// Type-safe property access
if (result.allowed) {
  console.log(result.remaining); // TypeScript knows remaining exists
}

Importing Types

Import types for use in your own code:

import type { 
  LimitlyConfig, 
  LimitlyResponse, 
  RateLimitOptions,
  LimitlyClient
} from 'limitly-sdk';

Typed Configuration

Create type-safe configuration:

import type { LimitlyConfig } from 'limitly-sdk';
import { createClient } from 'limitly-sdk';

// Fully typed configuration
const config: LimitlyConfig = {
  serviceId: 'my-app',
  timeout: 5000
};

const client = createClient(config);

Typed Responses

Work with typed responses:

import type { LimitlyResponse } from 'limitly-sdk';
import { createClient } from 'limitly-sdk';

// Recommended: Use your own Redis
const client = createClient({
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
  serviceId: 'my-app'
});

async function handleRequest(userId: string): Promise<LimitlyResponse> {
  const result: LimitlyResponse = await client.checkRateLimit(userId);
  
  if (!result.allowed) {
    // TypeScript knows reset might exist
    if (result.reset) {
      const resetDate = new Date(result.reset);
      console.log(`Reset at: ${resetDate.toISOString()}`);
    }
  }
  
  return result;
}

Type Guards

Create type guards for better type narrowing:

import type { LimitlyResponse } from 'limitly-sdk';

function isRateLimited(
  response: LimitlyResponse
): response is LimitlyResponse & { allowed: false } {
  return !response.allowed;
}

function isAllowed(
  response: LimitlyResponse
): response is LimitlyResponse & { allowed: true; remaining: number } {
  return response.allowed && response.remaining !== undefined;
}

// Usage
const result = await checkLimit('user-123');

if (isRateLimited(result)) {
  // TypeScript knows result.allowed is false
  console.log('Rate limited:', result.message);
} else if (isAllowed(result)) {
  // TypeScript knows result.allowed is true and remaining exists
  console.log('Allowed. Remaining:', result.remaining);
}

Typed Wrappers

Build type-safe wrappers around Limitly:

import type { LimitlyResponse, RateLimitOptions } from 'limitly-sdk';
import { createClient } from 'limitly-sdk';

interface ProtectedRouteOptions extends RateLimitOptions {
  userId: string;
  endpoint?: string;
}

async function protectedRoute(
  options: ProtectedRouteOptions
): Promise<LimitlyResponse> {
  const client = createClient({
    redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
    serviceId: 'api'
  });
  
  return client.checkRateLimit({
    identifier: options.endpoint 
      ? `${options.userId}:${options.endpoint}`
      : options.userId,
    capacity: options.capacity,
    refillRate: options.refillRate,
    skip: options.skip
  });
}

// Usage with full type safety
const result = await protectedRoute({
  userId: 'user-123',
  endpoint: '/api/data',
  capacity: 100,
  refillRate: 10
});

Generic Helpers

Create generic helper functions:

import type { LimitlyResponse } from 'limitly-sdk';
import { createClient } from 'limitly-sdk';

type RateLimitHandler<T> = (result: LimitlyResponse) => T;

async function withRateLimit<T>(
  identifier: string,
  onAllowed: RateLimitHandler<T>,
  onRateLimited: RateLimitHandler<T>
): Promise<T> {
  const client = createClient({
    redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
    serviceId: 'my-app'
  });
  const result = await client.checkRateLimit(identifier);
  
  return result.allowed ? onAllowed(result) : onRateLimited(result);
}

// Usage
const response = await withRateLimit(
  'user-123',
  (result) => ({ 
    success: true, 
    remaining: result.remaining! 
  }),
  (result) => ({ 
    success: false, 
    error: 'Rate limited',
    retryAfter: result.reset 
      ? Math.ceil((result.reset - Date.now()) / 1000) 
      : 60
  })
);

Framework Integration Types

Type-safe integration with frameworks:

// Next.js App Router
import type { LimitlyResponse } from 'limitly-sdk';
import { createClient } from 'limitly-sdk';
import { NextResponse } from 'next/server';

// Recommended: Use your own Redis
const client = createClient({
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
  serviceId: 'nextjs-api'
});

export async function GET(request: Request): Promise<NextResponse<LimitlyResponse | { error: string }>> {
  const userId = request.headers.get('x-user-id') || 'anonymous';
  const result = await client.checkRateLimit(userId);
  
  if (!result.allowed) {
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      { status: 429 }
    );
  }
  
  return NextResponse.json(result);
}

Strict Type Checking

Enable strict TypeScript settings for maximum type safety:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

With strict mode, TypeScript will catch potential issues:

const client = createClient({
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
  serviceId: 'my-app'
});
const result = await client.checkRateLimit('user-123');

// TypeScript error if strict: true
// Property 'remaining' may be undefined
console.log(result.remaining.toString());

// Correct way
if (result.remaining !== undefined) {
  console.log(result.remaining.toString());
}

Type Assertions

Use type assertions when you're certain about types:

const client = createClient({
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
  serviceId: 'my-app'
});
const result = await client.checkRateLimit('user-123');

// Type assertion when you know remaining exists
if (result.allowed && result.remaining !== undefined) {
  const remaining: number = result.remaining;
  console.log(remaining);
}

Utility Types

Create utility types for common patterns:

import type { LimitlyResponse } from 'limitly-sdk';

// Extract only the required fields
type RateLimitInfo = Pick<LimitlyResponse, 'allowed' | 'remaining' | 'limit'>;

// Make all fields required
type RequiredRateLimitResponse = Required<LimitlyResponse>;

// Create a response with guaranteed fields
interface GuaranteedResponse {
  allowed: boolean;
  remaining: number;
  limit: number;
  reset: number;
}

function toGuaranteedResponse(
  response: LimitlyResponse
): GuaranteedResponse {
  return {
    allowed: response.allowed,
    remaining: response.remaining ?? 0,
    limit: response.limit ?? 100,
    reset: response.reset ?? Date.now() + 60000
  };
}