@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.
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
- package/esm/deps/jsr.io/@cross/runtime/1.2.1/mod.js +480 -0
- package/esm/mod.d.ts +27 -0
- package/esm/mod.js +27 -0
- package/esm/package.json +3 -0
- package/esm/src/auth/oauth.d.ts +68 -0
- package/esm/src/auth/oauth.js +203 -0
- package/esm/src/auth/scopes.d.ts +52 -0
- package/esm/src/auth/scopes.js +71 -0
- package/esm/src/auth/token-store.d.ts +57 -0
- package/esm/src/auth/token-store.js +142 -0
- package/esm/src/client.d.ts +98 -0
- package/esm/src/client.js +235 -0
- package/esm/src/errors.d.ts +52 -0
- package/esm/src/errors.js +102 -0
- package/esm/src/http/deduplication.d.ts +33 -0
- package/esm/src/http/deduplication.js +96 -0
- package/esm/src/http/rate-limiter.d.ts +47 -0
- package/esm/src/http/rate-limiter.js +168 -0
- package/esm/src/http/request.d.ts +24 -0
- package/esm/src/http/request.js +158 -0
- package/esm/src/http/retry.d.ts +9 -0
- package/esm/src/http/retry.js +61 -0
- package/esm/src/resources/activities.d.ts +149 -0
- package/esm/src/resources/activities.js +189 -0
- package/esm/src/resources/athletes.d.ts +37 -0
- package/esm/src/resources/athletes.js +85 -0
- package/esm/src/resources/clubs.d.ts +45 -0
- package/esm/src/resources/clubs.js +71 -0
- package/esm/src/resources/gears.d.ts +17 -0
- package/esm/src/resources/gears.js +27 -0
- package/esm/src/resources/routes.d.ts +33 -0
- package/esm/src/resources/routes.js +71 -0
- package/esm/src/resources/segment-efforts.d.ts +38 -0
- package/esm/src/resources/segment-efforts.js +53 -0
- package/esm/src/resources/segments.d.ts +42 -0
- package/esm/src/resources/segments.js +67 -0
- package/esm/src/resources/streams.d.ts +44 -0
- package/esm/src/resources/streams.js +75 -0
- package/esm/src/resources/uploads.d.ts +41 -0
- package/esm/src/resources/uploads.js +79 -0
- package/esm/src/types/api.d.ts +9 -0
- package/esm/src/types/api.js +7 -0
- package/esm/src/types/common.d.ts +65 -0
- package/esm/src/types/common.js +4 -0
- package/esm/src/types/generated.d.ts +731 -0
- package/esm/src/types/generated.js +7 -0
- package/esm/src/utils/pagination.d.ts +45 -0
- package/esm/src/utils/pagination.js +112 -0
- package/esm/src/utils/transformers.d.ts +30 -0
- package/esm/src/utils/transformers.js +189 -0
- package/esm/src/utils/validators.d.ts +53 -0
- package/esm/src/utils/validators.js +84 -0
- package/package.json +40 -0
- package/script/_dnt.shims.d.ts +2 -0
- package/script/_dnt.shims.js +60 -0
- package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.d.ts +126 -0
- package/script/deps/jsr.io/@cross/runtime/1.2.1/mod.js +526 -0
- package/script/mod.d.ts +27 -0
- package/script/mod.js +73 -0
- package/script/package.json +3 -0
- package/script/src/auth/oauth.d.ts +68 -0
- package/script/src/auth/oauth.js +211 -0
- package/script/src/auth/scopes.d.ts +52 -0
- package/script/src/auth/scopes.js +79 -0
- package/script/src/auth/token-store.d.ts +57 -0
- package/script/src/auth/token-store.js +182 -0
- package/script/src/client.d.ts +98 -0
- package/script/src/client.js +239 -0
- package/script/src/errors.d.ts +52 -0
- package/script/src/errors.js +111 -0
- package/script/src/http/deduplication.d.ts +33 -0
- package/script/src/http/deduplication.js +100 -0
- package/script/src/http/rate-limiter.d.ts +47 -0
- package/script/src/http/rate-limiter.js +172 -0
- package/script/src/http/request.d.ts +24 -0
- package/script/src/http/request.js +161 -0
- package/script/src/http/retry.d.ts +9 -0
- package/script/src/http/retry.js +64 -0
- package/script/src/resources/activities.d.ts +149 -0
- package/script/src/resources/activities.js +193 -0
- package/script/src/resources/athletes.d.ts +37 -0
- package/script/src/resources/athletes.js +89 -0
- package/script/src/resources/clubs.d.ts +45 -0
- package/script/src/resources/clubs.js +75 -0
- package/script/src/resources/gears.d.ts +17 -0
- package/script/src/resources/gears.js +31 -0
- package/script/src/resources/routes.d.ts +33 -0
- package/script/src/resources/routes.js +75 -0
- package/script/src/resources/segment-efforts.d.ts +38 -0
- package/script/src/resources/segment-efforts.js +57 -0
- package/script/src/resources/segments.d.ts +42 -0
- package/script/src/resources/segments.js +71 -0
- package/script/src/resources/streams.d.ts +44 -0
- package/script/src/resources/streams.js +79 -0
- package/script/src/resources/uploads.d.ts +41 -0
- package/script/src/resources/uploads.js +83 -0
- package/script/src/types/api.d.ts +9 -0
- package/script/src/types/api.js +23 -0
- package/script/src/types/common.d.ts +65 -0
- package/script/src/types/common.js +5 -0
- package/script/src/types/generated.d.ts +731 -0
- package/script/src/types/generated.js +8 -0
- package/script/src/utils/pagination.d.ts +45 -0
- package/script/src/utils/pagination.js +118 -0
- package/script/src/utils/transformers.d.ts +30 -0
- package/script/src/utils/transformers.js +196 -0
- package/script/src/utils/validators.d.ts +53 -0
- package/script/src/utils/validators.js +92 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Strava API client
|
|
3
|
+
*/
|
|
4
|
+
import { request } from "./http/request.js";
|
|
5
|
+
import { RateLimiter } from "./http/rate-limiter.js";
|
|
6
|
+
import { RequestDeduplicator } from "./http/deduplication.js";
|
|
7
|
+
import { OAuthManager } from "./auth/oauth.js";
|
|
8
|
+
import { getDefaultTokenStore } from "./auth/token-store.js";
|
|
9
|
+
/**
|
|
10
|
+
* Main Strava API client class
|
|
11
|
+
*/
|
|
12
|
+
export class StravaClient {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
Object.defineProperty(this, "options", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "oauthManager", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "rateLimiter", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "deduplicator", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "_athletes", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "_activities", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(this, "_segments", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
writable: true,
|
|
54
|
+
value: void 0
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(this, "_segmentEfforts", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: void 0
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(this, "_clubs", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: void 0
|
|
67
|
+
});
|
|
68
|
+
Object.defineProperty(this, "_gears", {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: true,
|
|
72
|
+
value: void 0
|
|
73
|
+
});
|
|
74
|
+
Object.defineProperty(this, "_routes", {
|
|
75
|
+
enumerable: true,
|
|
76
|
+
configurable: true,
|
|
77
|
+
writable: true,
|
|
78
|
+
value: void 0
|
|
79
|
+
});
|
|
80
|
+
Object.defineProperty(this, "_uploads", {
|
|
81
|
+
enumerable: true,
|
|
82
|
+
configurable: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
value: void 0
|
|
85
|
+
});
|
|
86
|
+
Object.defineProperty(this, "_streams", {
|
|
87
|
+
enumerable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
writable: true,
|
|
90
|
+
value: void 0
|
|
91
|
+
});
|
|
92
|
+
this.options = {
|
|
93
|
+
baseUrl: options.baseUrl || "https://www.strava.com/api/v3",
|
|
94
|
+
timeout: options.timeout || 30000,
|
|
95
|
+
retries: options.retries || {},
|
|
96
|
+
rateLimitStrategy: options.rateLimitStrategy || "queue",
|
|
97
|
+
deduplicationWindow: options.deduplicationWindow || 5000,
|
|
98
|
+
normalizeKeys: options.normalizeKeys ?? true,
|
|
99
|
+
transformDates: options.transformDates ?? true,
|
|
100
|
+
flattenResponses: options.flattenResponses ?? true,
|
|
101
|
+
addComputedFields: options.addComputedFields ?? true,
|
|
102
|
+
tokenStore: options.tokenStore || getDefaultTokenStore(),
|
|
103
|
+
redirectUri: options.redirectUri,
|
|
104
|
+
clientId: options.clientId,
|
|
105
|
+
clientSecret: options.clientSecret,
|
|
106
|
+
};
|
|
107
|
+
this.oauthManager = new OAuthManager(this.options.clientId, this.options.clientSecret, this.options.tokenStore);
|
|
108
|
+
this.rateLimiter = new RateLimiter(this.options.rateLimitStrategy);
|
|
109
|
+
this.deduplicator = new RequestDeduplicator(this.options.deduplicationWindow);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Authenticate with authorization code
|
|
113
|
+
*/
|
|
114
|
+
async authenticate(credentials) {
|
|
115
|
+
await this.oauthManager.authenticate(credentials.code, credentials.redirectUri || this.options.redirectUri);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Refresh access token
|
|
119
|
+
*/
|
|
120
|
+
async refreshToken() {
|
|
121
|
+
await this.oauthManager.refreshToken();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get authorization URL
|
|
125
|
+
*/
|
|
126
|
+
getAuthorizationUrl(options) {
|
|
127
|
+
return this.oauthManager.getAuthorizationUrl({
|
|
128
|
+
redirectUri: options.redirectUri || this.options.redirectUri || "",
|
|
129
|
+
scope: options.scope,
|
|
130
|
+
state: options.state,
|
|
131
|
+
approvalPrompt: options.approvalPrompt,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get current access token (for direct fetch calls)
|
|
136
|
+
*/
|
|
137
|
+
async getAccessToken() {
|
|
138
|
+
const token = await this.oauthManager.getToken();
|
|
139
|
+
if (!token) {
|
|
140
|
+
throw new Error("Not authenticated. Call authenticate() first.");
|
|
141
|
+
}
|
|
142
|
+
return token.accessToken;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Low-level request method
|
|
146
|
+
*/
|
|
147
|
+
async request(config) {
|
|
148
|
+
const token = await this.oauthManager.getToken();
|
|
149
|
+
if (!token) {
|
|
150
|
+
throw new Error("Not authenticated. Call authenticate() first.");
|
|
151
|
+
}
|
|
152
|
+
return request(config, {
|
|
153
|
+
baseUrl: this.options.baseUrl,
|
|
154
|
+
timeout: this.options.timeout,
|
|
155
|
+
accessToken: token.accessToken,
|
|
156
|
+
rateLimiter: this.rateLimiter,
|
|
157
|
+
deduplicator: this.deduplicator,
|
|
158
|
+
retryConfig: this.options.retries,
|
|
159
|
+
normalizeKeys: this.options.normalizeKeys,
|
|
160
|
+
transformDates: this.options.transformDates,
|
|
161
|
+
flattenResponses: this.options.flattenResponses,
|
|
162
|
+
addComputedFields: this.options.addComputedFields,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
get athletes() {
|
|
166
|
+
if (!this._athletes) {
|
|
167
|
+
this._athletes = new AthletesResource(this);
|
|
168
|
+
}
|
|
169
|
+
return this._athletes;
|
|
170
|
+
}
|
|
171
|
+
get activities() {
|
|
172
|
+
if (!this._activities) {
|
|
173
|
+
this._activities = new ActivitiesResource(this);
|
|
174
|
+
}
|
|
175
|
+
return this._activities;
|
|
176
|
+
}
|
|
177
|
+
get segments() {
|
|
178
|
+
if (!this._segments) {
|
|
179
|
+
this._segments = new SegmentsResource(this);
|
|
180
|
+
}
|
|
181
|
+
return this._segments;
|
|
182
|
+
}
|
|
183
|
+
get segmentEfforts() {
|
|
184
|
+
if (!this._segmentEfforts) {
|
|
185
|
+
this._segmentEfforts = new SegmentEffortsResource(this);
|
|
186
|
+
}
|
|
187
|
+
return this._segmentEfforts;
|
|
188
|
+
}
|
|
189
|
+
get clubs() {
|
|
190
|
+
if (!this._clubs) {
|
|
191
|
+
this._clubs = new ClubsResource(this);
|
|
192
|
+
}
|
|
193
|
+
return this._clubs;
|
|
194
|
+
}
|
|
195
|
+
get gears() {
|
|
196
|
+
if (!this._gears) {
|
|
197
|
+
this._gears = new GearsResource(this);
|
|
198
|
+
}
|
|
199
|
+
return this._gears;
|
|
200
|
+
}
|
|
201
|
+
get routes() {
|
|
202
|
+
if (!this._routes) {
|
|
203
|
+
this._routes = new RoutesResource(this);
|
|
204
|
+
}
|
|
205
|
+
return this._routes;
|
|
206
|
+
}
|
|
207
|
+
get uploads() {
|
|
208
|
+
if (!this._uploads) {
|
|
209
|
+
this._uploads = new UploadsResource(this);
|
|
210
|
+
}
|
|
211
|
+
return this._uploads;
|
|
212
|
+
}
|
|
213
|
+
get streams() {
|
|
214
|
+
if (!this._streams) {
|
|
215
|
+
this._streams = new StreamsResource(this);
|
|
216
|
+
}
|
|
217
|
+
return this._streams;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Clean up resources (stop timers, clear caches)
|
|
221
|
+
* Call this when you're done with the client to allow the process to exit
|
|
222
|
+
*/
|
|
223
|
+
destroy() {
|
|
224
|
+
this.deduplicator.destroy();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
import { AthletesResource } from "./resources/athletes.js";
|
|
228
|
+
import { ActivitiesResource } from "./resources/activities.js";
|
|
229
|
+
import { SegmentsResource } from "./resources/segments.js";
|
|
230
|
+
import { SegmentEffortsResource } from "./resources/segment-efforts.js";
|
|
231
|
+
import { ClubsResource } from "./resources/clubs.js";
|
|
232
|
+
import { GearsResource } from "./resources/gears.js";
|
|
233
|
+
import { RoutesResource } from "./resources/routes.js";
|
|
234
|
+
import { UploadsResource } from "./resources/uploads.js";
|
|
235
|
+
import { StreamsResource } from "./resources/streams.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for Strava API interactions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all Strava API errors
|
|
6
|
+
*/
|
|
7
|
+
export declare class StravaError extends Error {
|
|
8
|
+
statusCode?: number;
|
|
9
|
+
response?: unknown;
|
|
10
|
+
constructor(message: string, statusCode?: number, response?: unknown);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Authentication-related errors (401, invalid credentials, token expired)
|
|
14
|
+
*/
|
|
15
|
+
export declare class StravaAuthError extends StravaError {
|
|
16
|
+
constructor(message: string, statusCode?: number, response?: unknown);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Rate limit errors (429)
|
|
20
|
+
*/
|
|
21
|
+
export declare class StravaRateLimitError extends StravaError {
|
|
22
|
+
retryAfter?: number;
|
|
23
|
+
limit?: number;
|
|
24
|
+
usage?: number;
|
|
25
|
+
constructor(message: string, statusCode?: number, response?: unknown, retryAfter?: number, limit?: number, usage?: number);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resource not found errors (404)
|
|
29
|
+
*/
|
|
30
|
+
export declare class StravaNotFoundError extends StravaError {
|
|
31
|
+
constructor(message: string, statusCode?: number, response?: unknown);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validation errors (422)
|
|
35
|
+
*/
|
|
36
|
+
export declare class StravaValidationError extends StravaError {
|
|
37
|
+
errors?: Array<{
|
|
38
|
+
field: string;
|
|
39
|
+
message: string;
|
|
40
|
+
}>;
|
|
41
|
+
constructor(message: string, statusCode?: number, response?: unknown, errors?: Array<{
|
|
42
|
+
field: string;
|
|
43
|
+
message: string;
|
|
44
|
+
}>);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Server errors (5xx)
|
|
48
|
+
*/
|
|
49
|
+
export declare class StravaServerError extends StravaError {
|
|
50
|
+
constructor(message: string, statusCode?: number, response?: unknown);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for Strava API interactions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all Strava API errors
|
|
6
|
+
*/
|
|
7
|
+
export class StravaError extends Error {
|
|
8
|
+
constructor(message, statusCode, response) {
|
|
9
|
+
super(message);
|
|
10
|
+
Object.defineProperty(this, "statusCode", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "response", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
22
|
+
this.name = "StravaError";
|
|
23
|
+
this.statusCode = statusCode;
|
|
24
|
+
this.response = response;
|
|
25
|
+
if (Error.captureStackTrace) {
|
|
26
|
+
Error.captureStackTrace(this, StravaError);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Authentication-related errors (401, invalid credentials, token expired)
|
|
32
|
+
*/
|
|
33
|
+
export class StravaAuthError extends StravaError {
|
|
34
|
+
constructor(message, statusCode, response) {
|
|
35
|
+
super(message, statusCode, response);
|
|
36
|
+
this.name = "StravaAuthError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rate limit errors (429)
|
|
41
|
+
*/
|
|
42
|
+
export class StravaRateLimitError extends StravaError {
|
|
43
|
+
constructor(message, statusCode, response, retryAfter, limit, usage) {
|
|
44
|
+
super(message, statusCode, response);
|
|
45
|
+
Object.defineProperty(this, "retryAfter", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: void 0
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(this, "limit", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
value: void 0
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(this, "usage", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: void 0
|
|
62
|
+
});
|
|
63
|
+
this.name = "StravaRateLimitError";
|
|
64
|
+
this.retryAfter = retryAfter;
|
|
65
|
+
this.limit = limit;
|
|
66
|
+
this.usage = usage;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resource not found errors (404)
|
|
71
|
+
*/
|
|
72
|
+
export class StravaNotFoundError extends StravaError {
|
|
73
|
+
constructor(message, statusCode, response) {
|
|
74
|
+
super(message, statusCode, response);
|
|
75
|
+
this.name = "StravaNotFoundError";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validation errors (422)
|
|
80
|
+
*/
|
|
81
|
+
export class StravaValidationError extends StravaError {
|
|
82
|
+
constructor(message, statusCode, response, errors) {
|
|
83
|
+
super(message, statusCode, response);
|
|
84
|
+
Object.defineProperty(this, "errors", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: void 0
|
|
89
|
+
});
|
|
90
|
+
this.name = "StravaValidationError";
|
|
91
|
+
this.errors = errors;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Server errors (5xx)
|
|
96
|
+
*/
|
|
97
|
+
export class StravaServerError extends StravaError {
|
|
98
|
+
constructor(message, statusCode, response) {
|
|
99
|
+
super(message, statusCode, response);
|
|
100
|
+
this.name = "StravaServerError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request deduplication to prevent duplicate requests within a time window
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Request deduplication cache
|
|
6
|
+
*/
|
|
7
|
+
export class RequestDeduplicator {
|
|
8
|
+
constructor(windowMs = 5000) {
|
|
9
|
+
Object.defineProperty(this, "cache", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: new Map()
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "cleanupInterval", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: null
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "windowMs", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: void 0
|
|
26
|
+
});
|
|
27
|
+
this.windowMs = windowMs;
|
|
28
|
+
this.startCleanup();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate cache key from request details
|
|
32
|
+
*/
|
|
33
|
+
generateKey(method, path, query, body) {
|
|
34
|
+
const parts = [method, path];
|
|
35
|
+
if (query) {
|
|
36
|
+
const sortedQuery = Object.keys(query)
|
|
37
|
+
.sort()
|
|
38
|
+
.map((key) => `${key}=${String(query[key])}`)
|
|
39
|
+
.join("&");
|
|
40
|
+
parts.push(sortedQuery);
|
|
41
|
+
}
|
|
42
|
+
if (body) {
|
|
43
|
+
try {
|
|
44
|
+
const bodyHash = JSON.stringify(body);
|
|
45
|
+
parts.push(bodyHash);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
parts.push(String(body));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return parts.join("|");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get or create a request promise
|
|
55
|
+
*/
|
|
56
|
+
getOrCreate(method, path, query, body, factory) {
|
|
57
|
+
const key = this.generateKey(method, path, query, body);
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const cached = this.cache.get(key);
|
|
60
|
+
if (cached && (now - cached.timestamp) < this.windowMs) {
|
|
61
|
+
return cached.promise;
|
|
62
|
+
}
|
|
63
|
+
const promise = factory();
|
|
64
|
+
this.cache.set(key, { promise, timestamp: now });
|
|
65
|
+
return promise;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clear expired entries
|
|
69
|
+
*/
|
|
70
|
+
cleanup() {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
73
|
+
if (now - entry.timestamp >= this.windowMs) {
|
|
74
|
+
this.cache.delete(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Start periodic cleanup
|
|
80
|
+
*/
|
|
81
|
+
startCleanup() {
|
|
82
|
+
this.cleanupInterval = setInterval(() => {
|
|
83
|
+
this.cleanup();
|
|
84
|
+
}, this.windowMs);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Stop cleanup and clear cache
|
|
88
|
+
*/
|
|
89
|
+
destroy() {
|
|
90
|
+
if (this.cleanupInterval !== null) {
|
|
91
|
+
clearInterval(this.cleanupInterval);
|
|
92
|
+
this.cleanupInterval = null;
|
|
93
|
+
}
|
|
94
|
+
this.cache.clear();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -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
|