()
| 588 | * on pass/skip; on a non-error outcome it populates `rateLimitInfo`. |
| 589 | */ |
| 590 | const runRateLimitGate = async (): Promise<GateFailure | null> => { |
| 591 | if (!checkRateLimit) return null |
| 592 | try { |
| 593 | const rateLimiter = new RateLimiter() |
| 594 | const info = await rateLimiter.checkRateLimitWithSubscription( |
| 595 | actorUserId, |
| 596 | userSubscription, |
| 597 | triggerType, |
| 598 | false // not async |
| 599 | ) |
| 600 | rateLimitInfo = info |
| 601 | |
| 602 | if (!info.allowed) { |
| 603 | logger.warn(`[${requestId}] Rate limit exceeded for user ${actorUserId}`, { |
| 604 | triggerType, |
| 605 | remaining: info.remaining, |
| 606 | resetAt: info.resetAt, |
| 607 | }) |
| 608 | |
| 609 | return { |
| 610 | response: { |
| 611 | success: false, |
| 612 | error: { |
| 613 | message: `Rate limit exceeded. Please try again later.`, |
| 614 | statusCode: 429, |
| 615 | logCreated: true, |
| 616 | }, |
| 617 | }, |
| 618 | recordError: { |
| 619 | workflowId, |
| 620 | executionId, |
| 621 | triggerType, |
| 622 | requestId, |
| 623 | userId: actorUserId, |
| 624 | workspaceId, |
| 625 | errorMessage: `Rate limit exceeded. ${info.remaining} requests remaining. Resets at ${info.resetAt.toISOString()}.`, |
| 626 | loggingSession: providedLoggingSession, |
| 627 | triggerData, |
| 628 | }, |
| 629 | } |
| 630 | } |
| 631 | return null |
| 632 | } catch (error) { |
| 633 | logger.error(`[${requestId}] Error checking rate limits`, { error, actorUserId }) |
| 634 | |
| 635 | return { |
| 636 | response: { |
| 637 | success: false, |
| 638 | error: { |
| 639 | message: 'Error checking rate limits', |
| 640 | statusCode: 500, |
| 641 | logCreated: true, |
| 642 | retryable: isRetryableInfrastructureError(error), |
| 643 | cause: describeRetryableInfrastructureError(error), |
| 644 | }, |
| 645 | }, |
| 646 | recordError: { |
| 647 | workflowId, |
no test coverage detected