| 98 | ]; |
| 99 | |
| 100 | export class VercelDriver extends DeploymentDriver<VercelCredentials, VercelOptions> { |
| 101 | private static readonly API_URL = 'https://api.vercel.com'; |
| 102 | private requestLimit = pLimit(5); |
| 103 | |
| 104 | constructor(credentials: VercelCredentials, options: VercelOptions = {}) { |
| 105 | super(credentials, options); |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Make authenticated request with retry on rate limit and concurrency control |
| 110 | */ |
| 111 | private async request<T>(endpoint: string, options: DeploymentRequestOptions = {}, retryCount = 0): Promise<T> { |
| 112 | return this.requestLimit(async () => { |
| 113 | const response = await this.axiosRequest<T>(VercelDriver.API_URL, endpoint, { |
| 114 | ...options, |
| 115 | headers: { |
| 116 | Authorization: `Bearer ${this.credentials.access_token}`, |
| 117 | 'Content-Type': 'application/json', |
| 118 | ...(options.headers ?? {}), |
| 119 | }, |
| 120 | params: { |
| 121 | ...(this.options.team_id ? { teamId: this.options.team_id } : {}), |
| 122 | ...(options.params ?? {}), |
| 123 | }, |
| 124 | }); |
| 125 | |
| 126 | // Handle rate limiting with retry (max 3 retries) |
| 127 | if (response.status === 429) { |
| 128 | const resetAt = parseInt(response.headers['x-ratelimit-reset'] || '0'); |
| 129 | const limit = parseInt(response.headers['x-ratelimit-limit'] || '0'); |
| 130 | |
| 131 | if (retryCount < 3) { |
| 132 | const waitTime = resetAt > 0 ? Math.max(resetAt * 1000 - Date.now(), 1000) : 1000 * (retryCount + 1); |
| 133 | await new Promise((resolve) => setTimeout(resolve, waitTime)); |
| 134 | return this.request(endpoint, options, retryCount + 1); |
| 135 | } |
| 136 | |
| 137 | // Max retries exceeded |
| 138 | throw new HitRateLimitError({ |
| 139 | limit, |
| 140 | reset: new Date(resetAt > 0 ? resetAt * 1000 : Date.now()), |
| 141 | }); |
| 142 | } |
| 143 | |
| 144 | const body = response.data; |
| 145 | |
| 146 | if (response.status >= 400) { |
| 147 | const message = |
| 148 | typeof body === 'object' && body !== null && 'error' in body |
| 149 | ? (body as { error?: { message?: string } }).error?.message || `Vercel API error: ${response.status}` |
| 150 | : `Vercel API error: ${response.status}`; |
| 151 | |
| 152 | if (response.status === 401 || response.status === 403) { |
| 153 | throw new InvalidCredentialsError(); |
| 154 | } |
| 155 | |
| 156 | throw new ServiceUnavailableError({ service: 'vercel', reason: message }); |
| 157 | } |
nothing calls this directly
no outgoing calls
no test coverage detected