gametime-api-client 1.2.1 → 1.3.0
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/README.md +2 -0
- package/dist/index.js +50 -1
- package/package.json +3 -13
package/README.md
CHANGED
|
@@ -107,6 +107,8 @@ const csv = await gametime.situations.exportCsv(id, 'nba');
|
|
|
107
107
|
writeFileSync('austin-reaves-q4-down-1-15.csv', csv);
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
`situations.create` automatically retries transient upstream failures (`429`, `502`, `503`, `504`) with capped exponential backoff. This keeps client usage simple, but heavily rate-limited periods can increase response time.
|
|
111
|
+
|
|
110
112
|
## API reference
|
|
111
113
|
|
|
112
114
|
| Method | Description |
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,11 @@ class ApiError extends Error {
|
|
|
22
22
|
this.name = "GametimeApiError";
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
const CREATE_SITUATION_RETRY = {
|
|
26
|
+
maxAttempts: 5,
|
|
27
|
+
baseDelayMs: 600,
|
|
28
|
+
maxDelayMs: 12000,
|
|
29
|
+
};
|
|
25
30
|
async function request(baseUrl, path, init) {
|
|
26
31
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
27
32
|
const res = await fetch(url, {
|
|
@@ -35,6 +40,25 @@ async function request(baseUrl, path, init) {
|
|
|
35
40
|
}
|
|
36
41
|
return body;
|
|
37
42
|
}
|
|
43
|
+
async function requestWithRetry(run, shouldRetry, options = {}) {
|
|
44
|
+
const maxAttempts = sanitizePositiveInt(options.maxAttempts, CREATE_SITUATION_RETRY.maxAttempts);
|
|
45
|
+
const baseDelayMs = sanitizePositiveInt(options.baseDelayMs, CREATE_SITUATION_RETRY.baseDelayMs);
|
|
46
|
+
const maxDelayMs = sanitizePositiveInt(options.maxDelayMs, CREATE_SITUATION_RETRY.maxDelayMs);
|
|
47
|
+
let lastError;
|
|
48
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
49
|
+
try {
|
|
50
|
+
return await run();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
lastError = error;
|
|
54
|
+
if (!shouldRetry(error) || attempt >= maxAttempts) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
await sleep(calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw lastError;
|
|
61
|
+
}
|
|
38
62
|
function get(baseUrl, path) {
|
|
39
63
|
return request(baseUrl, path, { method: "GET" });
|
|
40
64
|
}
|
|
@@ -77,7 +101,7 @@ export function createClient(options = {}) {
|
|
|
77
101
|
};
|
|
78
102
|
const situations = {
|
|
79
103
|
create(params) {
|
|
80
|
-
return post(baseUrl, "/v1/situations", params);
|
|
104
|
+
return requestWithRetry(() => post(baseUrl, "/v1/situations", params), isRetryableCreateSituationError);
|
|
81
105
|
},
|
|
82
106
|
analysis(id, sport) {
|
|
83
107
|
return get(baseUrl, `/v1/situations/${encodeURIComponent(id)}/analysis?sport=${sport}`);
|
|
@@ -94,3 +118,28 @@ export function createClient(options = {}) {
|
|
|
94
118
|
/** Pre-built client using default hosted API URL */
|
|
95
119
|
export const gametime = createClient();
|
|
96
120
|
export { ApiError };
|
|
121
|
+
function isRetryableCreateSituationError(error) {
|
|
122
|
+
if (!(error instanceof ApiError)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const status = error.status;
|
|
126
|
+
if (status === 429 || status === 502 || status === 503 || status === 504) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const upstreamStatus = error.details?.status;
|
|
130
|
+
return upstreamStatus === 429;
|
|
131
|
+
}
|
|
132
|
+
function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
133
|
+
const exponential = Math.min(maxDelayMs, baseDelayMs * (2 ** (attempt - 1)));
|
|
134
|
+
const jitter = Math.floor(Math.random() * Math.min(250, Math.max(1, Math.floor(exponential * 0.25))));
|
|
135
|
+
return Math.min(maxDelayMs, exponential + jitter);
|
|
136
|
+
}
|
|
137
|
+
function sanitizePositiveInt(value, fallback) {
|
|
138
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
139
|
+
return fallback;
|
|
140
|
+
}
|
|
141
|
+
return Math.floor(value);
|
|
142
|
+
}
|
|
143
|
+
function sleep(ms) {
|
|
144
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gametime-api-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "JavaScript client for Gametime API - live sports data, player analysis, and predictions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,22 +12,12 @@
|
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
15
|
+
"files": ["dist"],
|
|
18
16
|
"scripts": {
|
|
19
17
|
"build": "tsc",
|
|
20
18
|
"prepublishOnly": "npm run build"
|
|
21
19
|
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"gametime",
|
|
24
|
-
"sports",
|
|
25
|
-
"api",
|
|
26
|
-
"nba",
|
|
27
|
-
"soccer",
|
|
28
|
-
"live",
|
|
29
|
-
"prediction"
|
|
30
|
-
],
|
|
20
|
+
"keywords": ["gametime", "sports", "api", "nba", "soccer", "live", "prediction"],
|
|
31
21
|
"author": "",
|
|
32
22
|
"license": "MIT",
|
|
33
23
|
"devDependencies": {
|