@pinta365/strava 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +390 -0
  3. package/esm/_dnt.shims.d.ts +2 -0
  4. package/esm/_dnt.shims.js +57 -0
  5. package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
  6. package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.js +480 -0
  7. package/esm/mod.d.ts +27 -0
  8. package/esm/mod.js +27 -0
  9. package/esm/package.json +3 -0
  10. package/esm/src/auth/oauth.d.ts +68 -0
  11. package/esm/src/auth/oauth.js +203 -0
  12. package/esm/src/auth/scopes.d.ts +52 -0
  13. package/esm/src/auth/scopes.js +71 -0
  14. package/esm/src/auth/token-store.d.ts +57 -0
  15. package/esm/src/auth/token-store.js +142 -0
  16. package/esm/src/client.d.ts +98 -0
  17. package/esm/src/client.js +235 -0
  18. package/esm/src/errors.d.ts +52 -0
  19. package/esm/src/errors.js +102 -0
  20. package/esm/src/http/deduplication.d.ts +33 -0
  21. package/esm/src/http/deduplication.js +96 -0
  22. package/esm/src/http/rate-limiter.d.ts +47 -0
  23. package/esm/src/http/rate-limiter.js +168 -0
  24. package/esm/src/http/request.d.ts +24 -0
  25. package/esm/src/http/request.js +158 -0
  26. package/esm/src/http/retry.d.ts +9 -0
  27. package/esm/src/http/retry.js +61 -0
  28. package/esm/src/resources/activities.d.ts +149 -0
  29. package/esm/src/resources/activities.js +189 -0
  30. package/esm/src/resources/athletes.d.ts +37 -0
  31. package/esm/src/resources/athletes.js +85 -0
  32. package/esm/src/resources/clubs.d.ts +45 -0
  33. package/esm/src/resources/clubs.js +71 -0
  34. package/esm/src/resources/gears.d.ts +17 -0
  35. package/esm/src/resources/gears.js +27 -0
  36. package/esm/src/resources/routes.d.ts +33 -0
  37. package/esm/src/resources/routes.js +71 -0
  38. package/esm/src/resources/segment-efforts.d.ts +38 -0
  39. package/esm/src/resources/segment-efforts.js +53 -0
  40. package/esm/src/resources/segments.d.ts +42 -0
  41. package/esm/src/resources/segments.js +67 -0
  42. package/esm/src/resources/streams.d.ts +44 -0
  43. package/esm/src/resources/streams.js +75 -0
  44. package/esm/src/resources/uploads.d.ts +41 -0
  45. package/esm/src/resources/uploads.js +79 -0
  46. package/esm/src/types/api.d.ts +9 -0
  47. package/esm/src/types/api.js +7 -0
  48. package/esm/src/types/common.d.ts +65 -0
  49. package/esm/src/types/common.js +4 -0
  50. package/esm/src/types/generated.d.ts +731 -0
  51. package/esm/src/types/generated.js +7 -0
  52. package/esm/src/utils/pagination.d.ts +45 -0
  53. package/esm/src/utils/pagination.js +112 -0
  54. package/esm/src/utils/transformers.d.ts +30 -0
  55. package/esm/src/utils/transformers.js +189 -0
  56. package/esm/src/utils/validators.d.ts +53 -0
  57. package/esm/src/utils/validators.js +84 -0
  58. package/package.json +40 -0
  59. package/script/_dnt.shims.d.ts +2 -0
  60. package/script/_dnt.shims.js +60 -0
  61. package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
  62. package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.js +526 -0
  63. package/script/mod.d.ts +27 -0
  64. package/script/mod.js +73 -0
  65. package/script/package.json +3 -0
  66. package/script/src/auth/oauth.d.ts +68 -0
  67. package/script/src/auth/oauth.js +211 -0
  68. package/script/src/auth/scopes.d.ts +52 -0
  69. package/script/src/auth/scopes.js +79 -0
  70. package/script/src/auth/token-store.d.ts +57 -0
  71. package/script/src/auth/token-store.js +182 -0
  72. package/script/src/client.d.ts +98 -0
  73. package/script/src/client.js +239 -0
  74. package/script/src/errors.d.ts +52 -0
  75. package/script/src/errors.js +111 -0
  76. package/script/src/http/deduplication.d.ts +33 -0
  77. package/script/src/http/deduplication.js +100 -0
  78. package/script/src/http/rate-limiter.d.ts +47 -0
  79. package/script/src/http/rate-limiter.js +172 -0
  80. package/script/src/http/request.d.ts +24 -0
  81. package/script/src/http/request.js +161 -0
  82. package/script/src/http/retry.d.ts +9 -0
  83. package/script/src/http/retry.js +64 -0
  84. package/script/src/resources/activities.d.ts +149 -0
  85. package/script/src/resources/activities.js +193 -0
  86. package/script/src/resources/athletes.d.ts +37 -0
  87. package/script/src/resources/athletes.js +89 -0
  88. package/script/src/resources/clubs.d.ts +45 -0
  89. package/script/src/resources/clubs.js +75 -0
  90. package/script/src/resources/gears.d.ts +17 -0
  91. package/script/src/resources/gears.js +31 -0
  92. package/script/src/resources/routes.d.ts +33 -0
  93. package/script/src/resources/routes.js +75 -0
  94. package/script/src/resources/segment-efforts.d.ts +38 -0
  95. package/script/src/resources/segment-efforts.js +57 -0
  96. package/script/src/resources/segments.d.ts +42 -0
  97. package/script/src/resources/segments.js +71 -0
  98. package/script/src/resources/streams.d.ts +44 -0
  99. package/script/src/resources/streams.js +79 -0
  100. package/script/src/resources/uploads.d.ts +41 -0
  101. package/script/src/resources/uploads.js +83 -0
  102. package/script/src/types/api.d.ts +9 -0
  103. package/script/src/types/api.js +23 -0
  104. package/script/src/types/common.d.ts +65 -0
  105. package/script/src/types/common.js +5 -0
  106. package/script/src/types/generated.d.ts +731 -0
  107. package/script/src/types/generated.js +8 -0
  108. package/script/src/utils/pagination.d.ts +45 -0
  109. package/script/src/utils/pagination.js +118 -0
  110. package/script/src/utils/transformers.d.ts +30 -0
  111. package/script/src/utils/transformers.js +196 -0
  112. package/script/src/utils/validators.d.ts +53 -0
  113. package/script/src/utils/validators.js +92 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Request deduplication to prevent duplicate requests within a time window
3
+ */
4
+ /**
5
+ * Request deduplication cache
6
+ */
7
+ export declare class RequestDeduplicator {
8
+ private cache;
9
+ private cleanupInterval;
10
+ private readonly windowMs;
11
+ constructor(windowMs?: number);
12
+ /**
13
+ * Generate cache key from request details
14
+ */
15
+ private generateKey;
16
+ /**
17
+ * Get or create a request promise
18
+ */
19
+ getOrCreate<T>(method: string, path: string, query: Record<string, unknown> | undefined, body: unknown, factory: () => Promise<T>): Promise<T>;
20
+ /**
21
+ * Clear expired entries
22
+ */
23
+ private cleanup;
24
+ /**
25
+ * Start periodic cleanup
26
+ */
27
+ private startCleanup;
28
+ /**
29
+ * Stop cleanup and clear cache
30
+ */
31
+ destroy(): void;
32
+ }
33
+ //# sourceMappingURL=deduplication.d.ts.map
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * Request deduplication to prevent duplicate requests within a time window
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RequestDeduplicator = void 0;
7
+ /**
8
+ * Request deduplication cache
9
+ */
10
+ class RequestDeduplicator {
11
+ constructor(windowMs = 5000) {
12
+ Object.defineProperty(this, "cache", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: new Map()
17
+ });
18
+ Object.defineProperty(this, "cleanupInterval", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: null
23
+ });
24
+ Object.defineProperty(this, "windowMs", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ this.windowMs = windowMs;
31
+ this.startCleanup();
32
+ }
33
+ /**
34
+ * Generate cache key from request details
35
+ */
36
+ generateKey(method, path, query, body) {
37
+ const parts = [method, path];
38
+ if (query) {
39
+ const sortedQuery = Object.keys(query)
40
+ .sort()
41
+ .map((key) => `${key}=${String(query[key])}`)
42
+ .join("&");
43
+ parts.push(sortedQuery);
44
+ }
45
+ if (body) {
46
+ try {
47
+ const bodyHash = JSON.stringify(body);
48
+ parts.push(bodyHash);
49
+ }
50
+ catch {
51
+ parts.push(String(body));
52
+ }
53
+ }
54
+ return parts.join("|");
55
+ }
56
+ /**
57
+ * Get or create a request promise
58
+ */
59
+ getOrCreate(method, path, query, body, factory) {
60
+ const key = this.generateKey(method, path, query, body);
61
+ const now = Date.now();
62
+ const cached = this.cache.get(key);
63
+ if (cached && (now - cached.timestamp) < this.windowMs) {
64
+ return cached.promise;
65
+ }
66
+ const promise = factory();
67
+ this.cache.set(key, { promise, timestamp: now });
68
+ return promise;
69
+ }
70
+ /**
71
+ * Clear expired entries
72
+ */
73
+ cleanup() {
74
+ const now = Date.now();
75
+ for (const [key, entry] of this.cache.entries()) {
76
+ if (now - entry.timestamp >= this.windowMs) {
77
+ this.cache.delete(key);
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * Start periodic cleanup
83
+ */
84
+ startCleanup() {
85
+ this.cleanupInterval = setInterval(() => {
86
+ this.cleanup();
87
+ }, this.windowMs);
88
+ }
89
+ /**
90
+ * Stop cleanup and clear cache
91
+ */
92
+ destroy() {
93
+ if (this.cleanupInterval !== null) {
94
+ clearInterval(this.cleanupInterval);
95
+ this.cleanupInterval = null;
96
+ }
97
+ this.cache.clear();
98
+ }
99
+ }
100
+ exports.RequestDeduplicator = RequestDeduplicator;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Rate limit tracking and queue management
3
+ */
4
+ import type { RateLimitInfo, RateLimitStrategy } from "../types/common.js";
5
+ /**
6
+ * Rate limiter for Strava API
7
+ */
8
+ export declare class RateLimiter {
9
+ private shortTermLimit;
10
+ private shortTermWindow;
11
+ private dailyLimit;
12
+ private dailyWindow;
13
+ private shortTermRequests;
14
+ private dailyRequests;
15
+ private queue;
16
+ private strategy;
17
+ constructor(strategy?: RateLimitStrategy);
18
+ /**
19
+ * Update rate limit info from response headers
20
+ */
21
+ updateFromHeaders(headers: Headers): void;
22
+ /**
23
+ * Record a request timestamp
24
+ */
25
+ private recordRequest;
26
+ /**
27
+ * Get current rate limit info
28
+ */
29
+ getRateLimitInfo(): RateLimitInfo;
30
+ /**
31
+ * Check if we can make a request
32
+ */
33
+ canMakeRequest(): boolean;
34
+ /**
35
+ * Wait until we can make a request
36
+ */
37
+ waitForAvailability(): Promise<void>;
38
+ /**
39
+ * Process queued requests
40
+ */
41
+ processQueue(): void;
42
+ /**
43
+ * Set rate limit strategy
44
+ */
45
+ setStrategy(strategy: RateLimitStrategy): void;
46
+ }
47
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ /**
3
+ * Rate limit tracking and queue management
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RateLimiter = void 0;
7
+ /**
8
+ * Rate limiter for Strava API
9
+ */
10
+ class RateLimiter {
11
+ constructor(strategy = "queue") {
12
+ Object.defineProperty(this, "shortTermLimit", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: 600
17
+ });
18
+ Object.defineProperty(this, "shortTermWindow", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: 15 * 60 * 1000
23
+ });
24
+ Object.defineProperty(this, "dailyLimit", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: 30000
29
+ });
30
+ Object.defineProperty(this, "dailyWindow", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: 24 * 60 * 60 * 1000
35
+ });
36
+ Object.defineProperty(this, "shortTermRequests", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: []
41
+ });
42
+ Object.defineProperty(this, "dailyRequests", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: []
47
+ });
48
+ Object.defineProperty(this, "queue", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: []
53
+ });
54
+ Object.defineProperty(this, "strategy", {
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ value: void 0
59
+ });
60
+ this.strategy = strategy;
61
+ }
62
+ /**
63
+ * Update rate limit info from response headers
64
+ */
65
+ updateFromHeaders(headers) {
66
+ const shortTermLimit = headers.get("X-RateLimit-Limit");
67
+ const shortTermUsage = headers.get("X-RateLimit-Usage");
68
+ const dailyLimit = headers.get("X-RateLimit-Limit-Daily");
69
+ const dailyUsage = headers.get("X-RateLimit-Usage-Daily");
70
+ if (shortTermLimit) {
71
+ this.shortTermLimit = parseInt(shortTermLimit, 10);
72
+ }
73
+ if (shortTermUsage) {
74
+ const parts = shortTermUsage.split(",");
75
+ if (parts.length >= 1) {
76
+ const current = parseInt(parts[0], 10);
77
+ this.recordRequest("shortTerm", current);
78
+ }
79
+ }
80
+ if (dailyLimit) {
81
+ this.dailyLimit = parseInt(dailyLimit, 10);
82
+ }
83
+ if (dailyUsage) {
84
+ const parts = dailyUsage.split(",");
85
+ if (parts.length >= 1) {
86
+ const current = parseInt(parts[0], 10);
87
+ this.recordRequest("daily", current);
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Record a request timestamp
93
+ */
94
+ recordRequest(type, _currentUsage) {
95
+ const now = Date.now();
96
+ const window = type === "shortTerm" ? this.shortTermWindow : this.dailyWindow;
97
+ const requests = type === "shortTerm" ? this.shortTermRequests : this.dailyRequests;
98
+ const cutoff = now - window;
99
+ while (requests.length > 0 && requests[0] < cutoff) {
100
+ requests.shift();
101
+ }
102
+ requests.push(now);
103
+ }
104
+ /**
105
+ * Get current rate limit info
106
+ */
107
+ getRateLimitInfo() {
108
+ const now = Date.now();
109
+ const shortTermCutoff = now - this.shortTermWindow;
110
+ this.shortTermRequests = this.shortTermRequests.filter((t) => t >= shortTermCutoff);
111
+ const dailyCutoff = now - this.dailyWindow;
112
+ this.dailyRequests = this.dailyRequests.filter((t) => t >= dailyCutoff);
113
+ return {
114
+ shortTermLimit: this.shortTermLimit,
115
+ shortTermUsage: this.shortTermRequests.length,
116
+ dailyLimit: this.dailyLimit,
117
+ dailyUsage: this.dailyRequests.length,
118
+ };
119
+ }
120
+ /**
121
+ * Check if we can make a request
122
+ */
123
+ canMakeRequest() {
124
+ const info = this.getRateLimitInfo();
125
+ return (info.shortTermUsage ?? 0) < (info.shortTermLimit ?? 600) &&
126
+ (info.dailyUsage ?? 0) < (info.dailyLimit ?? 30000);
127
+ }
128
+ /**
129
+ * Wait until we can make a request
130
+ */
131
+ async waitForAvailability() {
132
+ if (this.canMakeRequest()) {
133
+ return;
134
+ }
135
+ if (this.strategy === "throw") {
136
+ const info = this.getRateLimitInfo();
137
+ throw new Error(`Rate limit exceeded. Short-term: ${info.shortTermUsage}/${info.shortTermLimit}, ` +
138
+ `Daily: ${info.dailyUsage}/${info.dailyLimit}`);
139
+ }
140
+ if (this.strategy === "wait") {
141
+ while (!this.canMakeRequest()) {
142
+ await new Promise((resolve) => setTimeout(resolve, 1000));
143
+ }
144
+ return;
145
+ }
146
+ return new Promise((resolve, reject) => {
147
+ this.queue.push({
148
+ resolve,
149
+ reject,
150
+ timestamp: Date.now(),
151
+ });
152
+ });
153
+ }
154
+ /**
155
+ * Process queued requests
156
+ */
157
+ processQueue() {
158
+ while (this.queue.length > 0 && this.canMakeRequest()) {
159
+ const request = this.queue.shift();
160
+ if (request) {
161
+ request.resolve(undefined);
162
+ }
163
+ }
164
+ }
165
+ /**
166
+ * Set rate limit strategy
167
+ */
168
+ setStrategy(strategy) {
169
+ this.strategy = strategy;
170
+ }
171
+ }
172
+ exports.RateLimiter = RateLimiter;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Core HTTP request handler
3
+ */
4
+ import type { RateLimiter } from "./rate-limiter.js";
5
+ import type { RequestDeduplicator } from "./deduplication.js";
6
+ import type { RequestConfig, RetryConfig } from "../types/common.js";
7
+ interface RequestOptions {
8
+ baseUrl?: string;
9
+ timeout?: number;
10
+ accessToken?: string;
11
+ rateLimiter?: RateLimiter;
12
+ deduplicator?: RequestDeduplicator;
13
+ retryConfig?: RetryConfig;
14
+ normalizeKeys?: boolean;
15
+ transformDates?: boolean;
16
+ flattenResponses?: boolean;
17
+ addComputedFields?: boolean;
18
+ }
19
+ /**
20
+ * Make HTTP request with retry, rate limiting, and error handling
21
+ */
22
+ export declare function request<T>(config: RequestConfig, options?: RequestOptions): Promise<T>;
23
+ export {};
24
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Core HTTP request handler
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.request = request;
7
+ const errors_js_1 = require("../errors.js");
8
+ const retry_js_1 = require("./retry.js");
9
+ const transformers_js_1 = require("../utils/transformers.js");
10
+ const DEFAULT_BASE_URL = "https://www.strava.com/api/v3";
11
+ const DEFAULT_TIMEOUT = 30000;
12
+ /**
13
+ * Extract rate limit info from response headers
14
+ */
15
+ function extractRateLimitInfo(headers) {
16
+ const shortTermLimit = headers.get("X-RateLimit-Limit");
17
+ const shortTermUsage = headers.get("X-RateLimit-Usage");
18
+ const dailyLimit = headers.get("X-RateLimit-Limit-Daily");
19
+ const dailyUsage = headers.get("X-RateLimit-Usage-Daily");
20
+ const info = {};
21
+ if (shortTermLimit) {
22
+ info.shortTermLimit = parseInt(shortTermLimit, 10);
23
+ }
24
+ if (shortTermUsage) {
25
+ const parts = shortTermUsage.split(",");
26
+ if (parts.length >= 1) {
27
+ info.shortTermUsage = parseInt(parts[0], 10);
28
+ }
29
+ }
30
+ if (dailyLimit) {
31
+ info.dailyLimit = parseInt(dailyLimit, 10);
32
+ }
33
+ if (dailyUsage) {
34
+ const parts = dailyUsage.split(",");
35
+ if (parts.length >= 1) {
36
+ info.dailyUsage = parseInt(parts[0], 10);
37
+ }
38
+ }
39
+ return info;
40
+ }
41
+ /**
42
+ * Build query string from query parameters
43
+ */
44
+ function buildQueryString(query) {
45
+ if (!query)
46
+ return "";
47
+ const params = new URLSearchParams();
48
+ for (const [key, value] of Object.entries(query)) {
49
+ if (value !== undefined && value !== null) {
50
+ params.append(key, String(value));
51
+ }
52
+ }
53
+ const queryString = params.toString();
54
+ return queryString ? `?${queryString}` : "";
55
+ }
56
+ /**
57
+ * Create error from response
58
+ */
59
+ async function createErrorFromResponse(response) {
60
+ const statusCode = response.status;
61
+ let errorData;
62
+ try {
63
+ errorData = await response.json();
64
+ }
65
+ catch {
66
+ errorData = await response.text().catch(() => undefined);
67
+ }
68
+ const message = typeof errorData === "object" && errorData !== null && "message" in errorData
69
+ ? String(errorData.message)
70
+ : `HTTP ${statusCode}: ${response.statusText}`;
71
+ const rateLimitInfo = extractRateLimitInfo(response.headers);
72
+ const retryAfter = response.headers.get("Retry-After");
73
+ switch (statusCode) {
74
+ case 401:
75
+ case 403:
76
+ return new errors_js_1.StravaAuthError(message, statusCode, errorData);
77
+ case 404:
78
+ return new errors_js_1.StravaNotFoundError(message, statusCode, errorData);
79
+ case 422: {
80
+ const errors = typeof errorData === "object" && errorData !== null && "errors" in errorData
81
+ ? errorData.errors
82
+ : undefined;
83
+ return new errors_js_1.StravaValidationError(message, statusCode, errorData, errors);
84
+ }
85
+ case 429:
86
+ return new errors_js_1.StravaRateLimitError(message, statusCode, errorData, retryAfter ? parseInt(retryAfter, 10) : undefined, rateLimitInfo.shortTermLimit, rateLimitInfo.shortTermUsage);
87
+ case 500:
88
+ case 502:
89
+ case 503:
90
+ case 504:
91
+ return new errors_js_1.StravaServerError(message, statusCode, errorData);
92
+ default:
93
+ return new errors_js_1.StravaError(message, statusCode, errorData);
94
+ }
95
+ }
96
+ /**
97
+ * Make HTTP request with retry, rate limiting, and error handling
98
+ */
99
+ function request(config, options = {}) {
100
+ const { baseUrl = DEFAULT_BASE_URL, timeout = DEFAULT_TIMEOUT, accessToken, rateLimiter, deduplicator, retryConfig, normalizeKeys = true, transformDates = false, flattenResponses = false, addComputedFields = false, } = options;
101
+ const queryString = buildQueryString(config.query);
102
+ const url = `${baseUrl}${config.path}${queryString}`;
103
+ const headers = {
104
+ "Content-Type": "application/json",
105
+ ...config.headers,
106
+ };
107
+ if (accessToken) {
108
+ headers["Authorization"] = `Bearer ${accessToken}`;
109
+ }
110
+ const makeRequest = async () => {
111
+ if (rateLimiter) {
112
+ await rateLimiter.waitForAvailability();
113
+ }
114
+ const controller = new AbortController();
115
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
116
+ try {
117
+ const response = await fetch(url, {
118
+ method: config.method,
119
+ headers,
120
+ body: config.body ? JSON.stringify(config.body) : undefined,
121
+ signal: controller.signal,
122
+ });
123
+ if (rateLimiter) {
124
+ rateLimiter.updateFromHeaders(response.headers);
125
+ rateLimiter.processQueue();
126
+ }
127
+ if (!response.ok) {
128
+ throw await createErrorFromResponse(response);
129
+ }
130
+ const rawData = await response.json();
131
+ const transformedData = (0, transformers_js_1.applyTransformations)(rawData, normalizeKeys, transformDates, flattenResponses, addComputedFields);
132
+ return transformedData;
133
+ }
134
+ catch (error) {
135
+ if (error instanceof errors_js_1.StravaError) {
136
+ throw error;
137
+ }
138
+ if (error instanceof Error && error.name === "AbortError") {
139
+ throw new errors_js_1.StravaError(`Request timeout after ${timeout}ms`, undefined, error);
140
+ }
141
+ throw new errors_js_1.StravaError(error instanceof Error ? error.message : "Unknown error", undefined, error);
142
+ }
143
+ finally {
144
+ clearTimeout(timeoutId);
145
+ }
146
+ };
147
+ if (deduplicator) {
148
+ return deduplicator.getOrCreate(config.method, config.path, config.query, config.body, () => (0, retry_js_1.withRetry)(makeRequest, retryConfig, (error) => {
149
+ if (error instanceof errors_js_1.StravaRateLimitError && error.retryAfter) {
150
+ return error.retryAfter;
151
+ }
152
+ return undefined;
153
+ }));
154
+ }
155
+ return (0, retry_js_1.withRetry)(makeRequest, retryConfig, (error) => {
156
+ if (error instanceof errors_js_1.StravaRateLimitError && error.retryAfter) {
157
+ return error.retryAfter;
158
+ }
159
+ return undefined;
160
+ });
161
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Retry logic with exponential backoff
3
+ */
4
+ import type { RetryConfig } from "../types/common.js";
5
+ /**
6
+ * Execute a function with retry logic
7
+ */
8
+ export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig, getRetryAfter?: (error: unknown) => number | undefined): Promise<T>;
9
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * Retry logic with exponential backoff
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withRetry = withRetry;
7
+ const DEFAULT_RETRY_CONFIG = {
8
+ maxAttempts: 3,
9
+ initialDelay: 1000,
10
+ maxDelay: 10000,
11
+ backoffFactor: 2,
12
+ retryableStatusCodes: [429, 500, 502, 503, 504],
13
+ };
14
+ /**
15
+ * Calculate delay for retry attempt
16
+ */
17
+ function calculateDelay(attempt, config, retryAfter) {
18
+ if (retryAfter !== undefined) {
19
+ return Math.min(retryAfter * 1000, config.maxDelay);
20
+ }
21
+ const delay = config.initialDelay * Math.pow(config.backoffFactor, attempt - 1);
22
+ return Math.min(delay, config.maxDelay);
23
+ }
24
+ /**
25
+ * Sleep for specified milliseconds
26
+ */
27
+ function sleep(ms) {
28
+ return new Promise((resolve) => setTimeout(resolve, ms));
29
+ }
30
+ /**
31
+ * Check if error/status code is retryable
32
+ */
33
+ function isRetryable(statusCode, config) {
34
+ if (statusCode === undefined) {
35
+ return true;
36
+ }
37
+ return config.retryableStatusCodes.includes(statusCode);
38
+ }
39
+ /**
40
+ * Execute a function with retry logic
41
+ */
42
+ async function withRetry(fn, config, getRetryAfter) {
43
+ const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
44
+ let lastError;
45
+ for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt++) {
46
+ try {
47
+ return await fn();
48
+ }
49
+ catch (error) {
50
+ lastError = error;
51
+ if (attempt >= retryConfig.maxAttempts) {
52
+ break;
53
+ }
54
+ const statusCode = error && typeof error === "object" && "statusCode" in error ? error.statusCode : undefined;
55
+ if (!isRetryable(statusCode, retryConfig)) {
56
+ throw error;
57
+ }
58
+ const retryAfter = getRetryAfter ? getRetryAfter(error) : undefined;
59
+ const delay = calculateDelay(attempt, retryConfig, retryAfter);
60
+ await sleep(delay);
61
+ }
62
+ }
63
+ throw lastError;
64
+ }