Skip to Content
⚠️Active Development Notice: TimeTiles is under active development. Information may be placeholder content or not up-to-date.

web


web / lib/services/quota-service

lib/services/quota-service

Service for managing user quotas and resource limits.

This service provides centralized control over user resource limits, usage tracking, and quota enforcement. It integrates with Payload CMS to enforce quotas and track usage across various operations like file uploads, scheduled imports, and event creation.

Usage Tracking Architecture

Usage tracking is stored in a separate user-usage collection rather than embedded in the users collection. This separation:

  • Prevents session-clearing issues that occurred when versioning was enabled on users
  • Isolates authentication data from usage tracking
  • Allows independent scaling and optimization of usage tracking

Quotas vs Rate Limiting

This service works alongside RateLimitService but serves a different purpose:

QuotaService (this service):

  • Purpose: Long-term resource management (fair usage, capacity planning)
  • Storage: Database (persistent, accurate) in user-usage collection
  • Scope: Per user ID
  • Time windows: Hours to lifetime (e.g., daily, total)
  • Reset: Fixed times (midnight UTC for daily quotas)
  • Examples: 10 uploads per day, 50,000 total events

RateLimitService:

  • Purpose: Short-term abuse prevention (DDoS, spam, burst attacks)
  • Storage: In-memory (fast, ephemeral)
  • Scope: Per IP address or identifier
  • Time windows: Seconds to hours
  • Reset: Sliding windows
  • Examples: 1 upload per 5 seconds, 5 per hour

Both checks typically run together - rate limits first (fast fail), then quotas (accurate tracking).

Example

// Typical usage pattern: check both rate limits and quotas import { getRateLimitService } from '@/lib/services/rate-limit-service'; import { getQuotaService } from '@/lib/services/quota-service'; // 1. Rate limit check (fast, prevents abuse) const rateLimitService = getRateLimitService(payload); const rateCheck = rateLimitService.checkTrustLevelRateLimit( clientIp, user, "FILE_UPLOAD" ); if (!rateCheck.allowed) { return res.status(429).json({ error: "Too many requests" }); } // 2. Quota check (accurate, tracks long-term usage) const quotaService = getQuotaService(payload); const quotaCheck = await quotaService.checkQuota( user, QUOTA_TYPES.FILE_UPLOADS_PER_DAY ); if (!quotaCheck.allowed) { throw new QuotaExceededError( quotaCheck.quotaType, quotaCheck.current, quotaCheck.limit, quotaCheck.resetTime ); } // 3. Process the request and track usage await processFileUpload(); await quotaService.incrementUsage(user.id, USAGE_TYPES.FILE_UPLOADS_TODAY, 1);

See

RateLimitService for short-term abuse prevention

Classes

QuotaExceededError

Custom error class for quota exceeded scenarios.

Extends

  • Error

Constructors

Constructor

new QuotaExceededError(quotaType, current, limit, resetTime?): QuotaExceededError

Parameters
quotaType

QuotaType

current

number

limit

number

resetTime?

Date

Returns

QuotaExceededError

Overrides

Error.constructor

Properties

prepareStackTrace()?

static optional prepareStackTrace: (err, stackTraces) => any

Optional override for formatting stack traces

Parameters
err

Error

stackTraces

CallSite[]

Returns

any

See

https://v8.dev/docs/stack-trace-api#customizing-stack-traces 

Inherited from

Error.prepareStackTrace

stackTraceLimit

static stackTraceLimit: number

Inherited from

Error.stackTraceLimit

cause?

optional cause: unknown

Inherited from

Error.cause

name

name: string

Inherited from

Error.name

message

message: string

Inherited from

Error.message

stack?

optional stack: string

Inherited from

Error.stack

statusCode

statusCode: number = 429

quotaType

quotaType: QuotaType

current

current: number

limit

limit: number

resetTime?

optional resetTime: Date

Methods

captureStackTrace()

static captureStackTrace(targetObject, constructorOpt?): void

Create .stack property on a target object

Parameters
targetObject

object

constructorOpt?

Function

Returns

void

Inherited from

Error.captureStackTrace


QuotaService

Service for managing user quotas and resource limits.

Constructors

Constructor

new QuotaService(payload): QuotaService

Parameters
payload

BasePayload

Returns

QuotaService

Methods

getOrCreateUsageRecord()

getOrCreateUsageRecord(userId): Promise<UserUsage>

Get or create usage record for a user from the user-usage collection. Uses upsert pattern to ensure usage record exists.

Parameters
userId

number

Returns

Promise<UserUsage>

getEffectiveQuotas()

getEffectiveQuotas(user): UserQuotas

Get effective quotas for a user, considering trust level and custom overrides.

Parameters
user

undefined | null | User

Returns

UserQuotas

getCurrentUsage()

getCurrentUsage(userId): Promise<null | UserUsage>

Get current usage for a user from the user-usage collection.

Parameters
userId

number

Returns

Promise<null | UserUsage>

checkQuota()

checkQuota(user, quotaType, amount): Promise<QuotaCheckResult>

Check if a user can perform an action based on quota limits. Now async since it reads from the separate user-usage collection.

Parameters
user

undefined | null | User

quotaType

QuotaType

amount

number = 1

Returns

Promise<QuotaCheckResult>

incrementUsage()

incrementUsage(userId, usageType, amount, _req?): Promise<void>

Increment usage counter for a user in the user-usage collection.

Parameters
userId

number

usageType

UsageType

amount

number = 1

_req?

PayloadRequest

Returns

Promise<void>

decrementUsage()

decrementUsage(userId, usageType, amount, _req?): Promise<void>

Decrement usage counter for a user (e.g., when a schedule is disabled).

Parameters
userId

number

usageType

UsageType

amount

number = 1

_req?

PayloadRequest

Returns

Promise<void>

resetDailyCounters()

resetDailyCounters(userId, _req?): Promise<void>

Reset daily counters for a user.

Parameters
userId

number

_req?

PayloadRequest

Returns

Promise<void>

resetAllDailyCounters()

resetAllDailyCounters(): Promise<void>

Reset daily counters for all users (called by background job).

Uses Payload’s bulk update API with an empty where clause to update all user-usage records in a single operation.

Returns

Promise<void>

validateQuota()

validateQuota(user, quotaType, amount): Promise<void>

Validate a quota check and throw if exceeded. Now async since checkQuota is async.

Parameters
user

undefined | null | User

quotaType

QuotaType

amount

number = 1

Returns

Promise<void>

getQuotaHeaders()

getQuotaHeaders(user, quotaType?): Promise<Record<string, string>>

Get minimal quota headers for HTTP responses. Now async since checkQuota is async.

Security: Only returns operation-specific rate limit info, does not expose:

  • Trust levels (internal scoring system)
  • Detailed quotas across all types (system architecture)
  • Exact reset times (rate limiting strategy)
Parameters
user

undefined | null | User

quotaType?

QuotaType

Returns

Promise<Record<string, string>>

Interfaces

QuotaCheckResult

Result of a quota check operation.

Properties

allowed

allowed: boolean

current

current: number

limit

limit: number

remaining

remaining: number

resetTime?

optional resetTime: Date

quotaType

quotaType: QuotaType

Functions

getQuotaService()

getQuotaService(payload): QuotaService

Get or create the quota service instance.

Parameters

payload

BasePayload

Returns

QuotaService

Last updated on