| 349 | } |
| 350 | |
| 351 | async function fetchRowRange( |
| 352 | accessToken: string, |
| 353 | spreadsheetId: string, |
| 354 | sheetName: string, |
| 355 | startRow: number, |
| 356 | endRow: number, |
| 357 | valueRenderOption: ValueRenderOption, |
| 358 | dateTimeRenderOption: DateTimeRenderOption, |
| 359 | requestId: string, |
| 360 | logger: Logger |
| 361 | ): Promise<string[][]> { |
| 362 | const encodedSheet = encodeURIComponent(sheetName) |
| 363 | const params = new URLSearchParams({ |
| 364 | fields: 'values', |
| 365 | valueRenderOption, |
| 366 | dateTimeRenderOption, |
| 367 | }) |
| 368 | const url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodedSheet}!${startRow}:${endRow}?${params.toString()}` |
| 369 | |
| 370 | const response = await fetch(url, { |
| 371 | headers: { Authorization: `Bearer ${accessToken}` }, |
| 372 | }) |
| 373 | |
| 374 | if (!response.ok) { |
| 375 | const status = response.status |
| 376 | const errorData = await response.json().catch(() => ({})) |
| 377 | if (status === 403 || status === 429) { |
| 378 | throw new Error( |
| 379 | `Sheets API rate limit (${status}) — skipping to retry next poll cycle: ${JSON.stringify(errorData)}` |
| 380 | ) |
| 381 | } |
| 382 | throw new Error( |
| 383 | `Failed to fetch rows ${startRow}-${endRow}: ${status} ${response.statusText} - ${JSON.stringify(errorData)}` |
| 384 | ) |
| 385 | } |
| 386 | |
| 387 | const data = await response.json() |
| 388 | return (data.values as string[][]) ?? [] |
| 389 | } |
| 390 | |
| 391 | async function processRows( |
| 392 | rows: string[][], |