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-usagecollection - 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
current
number
limit
number
resetTime?
Date
Returns
Overrides
Error.constructor
Properties
prepareStackTrace()?
staticoptionalprepareStackTrace: (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
staticstackTraceLimit:number
Inherited from
Error.stackTraceLimit
cause?
optionalcause:unknown
Inherited from
Error.cause
name
name:
string
Inherited from
Error.name
message
message:
string
Inherited from
Error.message
stack?
optionalstack:string
Inherited from
Error.stack
statusCode
statusCode:
number=429
quotaType
quotaType:
QuotaType
current
current:
number
limit
limit:
number
resetTime?
optionalresetTime:Date
Methods
captureStackTrace()
staticcaptureStackTrace(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
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
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
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
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
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
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?
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?
optionalresetTime:Date
quotaType
quotaType:
QuotaType
Functions
getQuotaService()
getQuotaService(
payload):QuotaService
Get or create the quota service instance.
Parameters
payload
BasePayload