@sentry/api 1.0.0 → 1.1.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 +11 -0
- package/dist/api-client/endpoints/index.js +383 -193
- package/dist/api-client/index.js +549 -324
- package/dist/browser.js +9 -10
- package/dist/index.js +4765 -1216
- package/dist/operations.js +5191 -5620
- package/dist/source-analysis.js +127 -465
- package/dist/types.gen.d.ts +0 -8
- package/dist/valibot.gen.d.ts +0 -3
- package/dist/valibot.js +1261 -1263
- package/dist/zod.gen.d.ts +0 -3
- package/dist/zod.js +1261 -1263
- package/package.json +5 -8
package/dist/api-client/index.js
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
// src/api-client/errors.ts
|
|
2
2
|
import { SentryApiError } from "@sentry/api";
|
|
3
|
-
|
|
4
|
-
class UserInputError extends Error {
|
|
3
|
+
var UserInputError = class extends Error {
|
|
5
4
|
constructor(message, options) {
|
|
6
5
|
super(message, options);
|
|
7
6
|
this.name = "UserInputError";
|
|
8
7
|
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class ConfigurationError extends Error {
|
|
8
|
+
};
|
|
9
|
+
var ConfigurationError = class extends Error {
|
|
12
10
|
constructor(message, options) {
|
|
13
11
|
super(message, options);
|
|
14
12
|
this.name = "ConfigurationError";
|
|
15
13
|
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
responseBody;
|
|
21
|
-
constructor(message, status, detail, responseBody) {
|
|
22
|
-
super(status, responseBody, undefined, false, message);
|
|
14
|
+
};
|
|
15
|
+
var ApiError = class _ApiError extends SentryApiError {
|
|
16
|
+
constructor(message, status, detail, responseBody2) {
|
|
17
|
+
super(status, responseBody2, void 0, false, message);
|
|
23
18
|
this.detail = detail;
|
|
24
|
-
this.responseBody =
|
|
19
|
+
this.responseBody = responseBody2;
|
|
25
20
|
this.name = "ApiError";
|
|
26
|
-
Object.setPrototypeOf(this,
|
|
21
|
+
Object.setPrototypeOf(this, _ApiError.prototype);
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
detail;
|
|
24
|
+
responseBody;
|
|
25
|
+
};
|
|
26
|
+
var ApiClientError = class _ApiClientError extends ApiError {
|
|
27
|
+
constructor(message, status, detail, responseBody2) {
|
|
28
|
+
super(message, status, detail, responseBody2);
|
|
33
29
|
this.name = "ApiClientError";
|
|
34
|
-
Object.setPrototypeOf(this,
|
|
30
|
+
Object.setPrototypeOf(this, _ApiClientError.prototype);
|
|
35
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert to a user-friendly formatted message.
|
|
34
|
+
* Returns the standard format: "API error (status): message" or just "API error: message" if no status.
|
|
35
|
+
* For 404 errors, adds helpful context about checking parameters.
|
|
36
|
+
*/
|
|
36
37
|
toUserMessage() {
|
|
37
38
|
const baseMessage = this.status ? `API error (${this.status}): ${this.message}` : `API error: ${this.message}`;
|
|
38
39
|
if (this.status === 404) {
|
|
@@ -42,7 +43,9 @@ class ApiClientError extends ApiError {
|
|
|
42
43
|
"resource does not exist",
|
|
43
44
|
"resource not found"
|
|
44
45
|
];
|
|
45
|
-
const isGeneric = genericMessages.some(
|
|
46
|
+
const isGeneric = genericMessages.some(
|
|
47
|
+
(msg) => this.message.toLowerCase() === msg || this.message.toLowerCase().includes("requested resource")
|
|
48
|
+
);
|
|
46
49
|
if (isGeneric) {
|
|
47
50
|
return `${baseMessage}. Please verify that the organization, project, or resource ID is correct and that you have access to it.`;
|
|
48
51
|
}
|
|
@@ -50,134 +53,160 @@ class ApiClientError extends ApiError {
|
|
|
50
53
|
}
|
|
51
54
|
return baseMessage;
|
|
52
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if this is a permission/authorization error (403)
|
|
58
|
+
*/
|
|
53
59
|
isPermissionError() {
|
|
54
60
|
return this.status === 403;
|
|
55
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if this is a not found error (404)
|
|
64
|
+
*/
|
|
56
65
|
isNotFoundError() {
|
|
57
66
|
return this.status === 404;
|
|
58
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if this is a validation error (400 or 422)
|
|
70
|
+
*/
|
|
59
71
|
isValidationError() {
|
|
60
72
|
return this.status === 400 || this.status === 422;
|
|
61
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if this is an authentication error (401)
|
|
76
|
+
*/
|
|
62
77
|
isAuthenticationError() {
|
|
63
78
|
return this.status === 401;
|
|
64
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if this is a rate limit error (429)
|
|
82
|
+
*/
|
|
65
83
|
isRateLimitError() {
|
|
66
84
|
return this.status === 429;
|
|
67
85
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
super(message, 403, detail, responseBody);
|
|
86
|
+
};
|
|
87
|
+
var ApiPermissionError = class _ApiPermissionError extends ApiClientError {
|
|
88
|
+
constructor(message, detail, responseBody2) {
|
|
89
|
+
super(message, 403, detail, responseBody2);
|
|
73
90
|
this.name = "ApiPermissionError";
|
|
74
|
-
Object.setPrototypeOf(this,
|
|
91
|
+
Object.setPrototypeOf(this, _ApiPermissionError.prototype);
|
|
75
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if this is the specific multi-project access error
|
|
95
|
+
*/
|
|
76
96
|
isMultiProjectAccessError() {
|
|
77
|
-
return this.message.includes("multiple projects") || this.message.includes("multi project") || this.message.includes("multi-project") || this.message.includes(
|
|
97
|
+
return this.message.includes("multiple projects") || this.message.includes("multi project") || this.message.includes("multi-project") || this.message.includes(
|
|
98
|
+
"You do not have access to query across multiple projects"
|
|
99
|
+
);
|
|
78
100
|
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
resourceId;
|
|
84
|
-
constructor(message, detail, responseBody, resourceType, resourceId) {
|
|
85
|
-
super(message, 404, detail, responseBody);
|
|
101
|
+
};
|
|
102
|
+
var ApiNotFoundError = class _ApiNotFoundError extends ApiClientError {
|
|
103
|
+
constructor(message, detail, responseBody2, resourceType, resourceId) {
|
|
104
|
+
super(message, 404, detail, responseBody2);
|
|
86
105
|
this.resourceType = resourceType;
|
|
87
106
|
this.resourceId = resourceId;
|
|
88
107
|
this.name = "ApiNotFoundError";
|
|
89
|
-
Object.setPrototypeOf(this,
|
|
108
|
+
Object.setPrototypeOf(this, _ApiNotFoundError.prototype);
|
|
90
109
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
constructor(message, status, detail,
|
|
96
|
-
super(message, status, detail,
|
|
110
|
+
resourceType;
|
|
111
|
+
resourceId;
|
|
112
|
+
};
|
|
113
|
+
var ApiValidationError = class _ApiValidationError extends ApiClientError {
|
|
114
|
+
constructor(message, status, detail, responseBody2, validationErrors) {
|
|
115
|
+
super(message, status, detail, responseBody2);
|
|
97
116
|
this.validationErrors = validationErrors;
|
|
98
117
|
this.name = "ApiValidationError";
|
|
99
|
-
Object.setPrototypeOf(this,
|
|
118
|
+
Object.setPrototypeOf(this, _ApiValidationError.prototype);
|
|
100
119
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
constructor(message, detail,
|
|
105
|
-
super(message, 401, detail,
|
|
120
|
+
validationErrors;
|
|
121
|
+
};
|
|
122
|
+
var ApiAuthenticationError = class _ApiAuthenticationError extends ApiClientError {
|
|
123
|
+
constructor(message, detail, responseBody2) {
|
|
124
|
+
super(message, 401, detail, responseBody2);
|
|
106
125
|
this.name = "ApiAuthenticationError";
|
|
107
|
-
Object.setPrototypeOf(this,
|
|
126
|
+
Object.setPrototypeOf(this, _ApiAuthenticationError.prototype);
|
|
108
127
|
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
constructor(message, detail, responseBody, retryAfterMs) {
|
|
114
|
-
super(message, 429, detail, responseBody);
|
|
128
|
+
};
|
|
129
|
+
var ApiRateLimitError = class _ApiRateLimitError extends ApiClientError {
|
|
130
|
+
constructor(message, detail, responseBody2, retryAfterMs) {
|
|
131
|
+
super(message, 429, detail, responseBody2);
|
|
115
132
|
this.retryAfterMs = retryAfterMs;
|
|
116
133
|
this.name = "ApiRateLimitError";
|
|
117
|
-
Object.setPrototypeOf(this,
|
|
134
|
+
Object.setPrototypeOf(this, _ApiRateLimitError.prototype);
|
|
118
135
|
}
|
|
136
|
+
retryAfterMs;
|
|
119
137
|
toUserMessage() {
|
|
120
|
-
if (this.retryAfterMs ===
|
|
138
|
+
if (this.retryAfterMs === void 0) {
|
|
121
139
|
return `${super.toUserMessage()}. Retry in a few seconds.`;
|
|
122
140
|
}
|
|
123
|
-
const seconds = Math.max(1, Math.ceil(this.retryAfterMs /
|
|
141
|
+
const seconds = Math.max(1, Math.ceil(this.retryAfterMs / 1e3));
|
|
124
142
|
return `${super.toUserMessage()}. Retry in ${seconds} second${seconds === 1 ? "" : "s"}.`;
|
|
125
143
|
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
super(message, status, detail, responseBody);
|
|
144
|
+
};
|
|
145
|
+
var ApiServerError = class _ApiServerError extends ApiError {
|
|
146
|
+
constructor(message, status, detail, responseBody2) {
|
|
147
|
+
super(message, status, detail, responseBody2);
|
|
131
148
|
this.name = "ApiServerError";
|
|
132
|
-
Object.setPrototypeOf(this,
|
|
149
|
+
Object.setPrototypeOf(this, _ApiServerError.prototype);
|
|
133
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if this is a gateway/proxy error (502, 503, 504)
|
|
153
|
+
*/
|
|
134
154
|
isGatewayError() {
|
|
135
155
|
return this.status === 502 || this.status === 503 || this.status === 504;
|
|
136
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if this is an internal server error (500)
|
|
159
|
+
*/
|
|
137
160
|
isInternalError() {
|
|
138
161
|
return this.status === 500;
|
|
139
162
|
}
|
|
140
|
-
}
|
|
141
|
-
function createApiError(message, status, detail,
|
|
163
|
+
};
|
|
164
|
+
function createApiError(message, status, detail, responseBody2, retryAfterMs) {
|
|
142
165
|
let improvedMessage = message;
|
|
143
|
-
if (message.includes(
|
|
166
|
+
if (message.includes(
|
|
167
|
+
"You do not have the multi project stream feature enabled"
|
|
168
|
+
) || message.includes("You cannot view events from multiple projects")) {
|
|
144
169
|
improvedMessage = "You do not have access to query across multiple projects. Please select a project for your query.";
|
|
145
|
-
return new ApiPermissionError(improvedMessage, detail,
|
|
170
|
+
return new ApiPermissionError(improvedMessage, detail, responseBody2);
|
|
146
171
|
}
|
|
147
172
|
switch (status) {
|
|
148
173
|
case 401:
|
|
149
|
-
return new ApiAuthenticationError(message, detail,
|
|
174
|
+
return new ApiAuthenticationError(message, detail, responseBody2);
|
|
150
175
|
case 403:
|
|
151
|
-
return new ApiPermissionError(message, detail,
|
|
176
|
+
return new ApiPermissionError(message, detail, responseBody2);
|
|
152
177
|
case 404:
|
|
153
|
-
return new ApiNotFoundError(message, detail,
|
|
178
|
+
return new ApiNotFoundError(message, detail, responseBody2);
|
|
154
179
|
case 400:
|
|
155
180
|
case 422: {
|
|
156
181
|
let validationErrors;
|
|
157
|
-
if (
|
|
158
|
-
validationErrors =
|
|
182
|
+
if (responseBody2 && typeof responseBody2 === "object" && "errors" in responseBody2) {
|
|
183
|
+
validationErrors = responseBody2.errors;
|
|
159
184
|
}
|
|
160
|
-
return new ApiValidationError(
|
|
185
|
+
return new ApiValidationError(
|
|
186
|
+
message,
|
|
187
|
+
status,
|
|
188
|
+
detail,
|
|
189
|
+
responseBody2,
|
|
190
|
+
validationErrors
|
|
191
|
+
);
|
|
161
192
|
}
|
|
162
193
|
case 429:
|
|
163
|
-
return new ApiRateLimitError(message, detail,
|
|
194
|
+
return new ApiRateLimitError(message, detail, responseBody2, retryAfterMs);
|
|
164
195
|
default:
|
|
165
196
|
if (status >= 400 && status < 500) {
|
|
166
|
-
return new ApiClientError(message, status, detail,
|
|
197
|
+
return new ApiClientError(message, status, detail, responseBody2);
|
|
167
198
|
}
|
|
168
199
|
if (status >= 500 && status < 600) {
|
|
169
|
-
return new ApiServerError(message, status, detail,
|
|
200
|
+
return new ApiServerError(message, status, detail, responseBody2);
|
|
170
201
|
}
|
|
171
|
-
return new ApiError(message, status, detail,
|
|
202
|
+
return new ApiError(message, status, detail, responseBody2);
|
|
172
203
|
}
|
|
173
204
|
}
|
|
174
205
|
function isApiAuthenticationErrorDeep(error) {
|
|
175
206
|
let current = error;
|
|
176
|
-
for (let i = 0;i < 3; i++) {
|
|
177
|
-
if (current instanceof ApiAuthenticationError)
|
|
178
|
-
|
|
179
|
-
if (!(current instanceof Error))
|
|
180
|
-
return false;
|
|
207
|
+
for (let i = 0; i < 3; i++) {
|
|
208
|
+
if (current instanceof ApiAuthenticationError) return true;
|
|
209
|
+
if (!(current instanceof Error)) return false;
|
|
181
210
|
current = current.cause;
|
|
182
211
|
}
|
|
183
212
|
return false;
|
|
@@ -204,7 +233,9 @@ function apiPath(strings, ...values) {
|
|
|
204
233
|
const value = String(values[index]);
|
|
205
234
|
const encoded = encodeApiPathSegment(value);
|
|
206
235
|
if (encoded === null) {
|
|
207
|
-
throw new UserInputError(
|
|
236
|
+
throw new UserInputError(
|
|
237
|
+
`Invalid identifier "${value}": relative path segments are not allowed.`
|
|
238
|
+
);
|
|
208
239
|
}
|
|
209
240
|
return acc + literal + encoded;
|
|
210
241
|
}, "");
|
|
@@ -213,6 +244,7 @@ function withQuery(path, query) {
|
|
|
213
244
|
const queryString = query.toString();
|
|
214
245
|
return queryString ? `${path}?${queryString}` : path;
|
|
215
246
|
}
|
|
247
|
+
|
|
216
248
|
// src/api-client/auth-token.ts
|
|
217
249
|
var INVALID_TOKEN_CHARACTER_PATTERN = /[^\x21-\x7e]/;
|
|
218
250
|
var TOKEN_PADDING_PATTERN = /[\s\x00-\x1f\x7f]/;
|
|
@@ -235,6 +267,7 @@ function sentryBearerHeader(token) {
|
|
|
235
267
|
const normalized = normalizeAuthToken(token);
|
|
236
268
|
return normalized === null ? null : `Bearer ${normalized}`;
|
|
237
269
|
}
|
|
270
|
+
|
|
238
271
|
// src/api-client/core.ts
|
|
239
272
|
import {
|
|
240
273
|
createSentryClient,
|
|
@@ -259,9 +292,10 @@ function encodePageCursor(cursor, skip) {
|
|
|
259
292
|
const next = skip > 0 ? NEXT_CURSOR_RE.exec(server) : null;
|
|
260
293
|
return next ? `${next[1]}:${encodeSkip(skip)}` : server;
|
|
261
294
|
}
|
|
262
|
-
function decodePageCursor(raw, warn = () => {
|
|
295
|
+
function decodePageCursor(raw, warn = () => {
|
|
296
|
+
}) {
|
|
263
297
|
if (!raw) {
|
|
264
|
-
return { cursor:
|
|
298
|
+
return { cursor: void 0, skip: 0 };
|
|
265
299
|
}
|
|
266
300
|
if (raw.startsWith(CLI_CURSOR_PREFIX)) {
|
|
267
301
|
return decodeCliCursor(raw, warn);
|
|
@@ -269,72 +303,86 @@ function decodePageCursor(raw, warn = () => {}) {
|
|
|
269
303
|
const next = NEXT_CURSOR_RE.exec(raw);
|
|
270
304
|
const cursor = next ? `${next[1]}:0` : raw;
|
|
271
305
|
const position = {
|
|
272
|
-
cursor: cursor === FIRST_PAGE_CURSOR ?
|
|
306
|
+
cursor: cursor === FIRST_PAGE_CURSOR ? void 0 : cursor,
|
|
273
307
|
skip: next?.[2] ? decodeSkip(next[2]) : 0
|
|
274
308
|
};
|
|
275
309
|
const perPage = serverCursorPageSize(cursor);
|
|
276
|
-
return perPage ===
|
|
310
|
+
return perPage === void 0 ? position : { ...position, perPage };
|
|
277
311
|
}
|
|
278
312
|
function decodeCliCursor(raw, warn) {
|
|
279
313
|
const [perPage, skip, ...server] = raw.slice(CLI_CURSOR_PREFIX.length).split("~");
|
|
280
314
|
const position = {
|
|
281
315
|
perPage: Number(perPage),
|
|
282
316
|
skip: Number(skip),
|
|
283
|
-
cursor: server.join("~") ||
|
|
317
|
+
cursor: server.join("~") || void 0
|
|
284
318
|
};
|
|
285
319
|
if (Number.isInteger(position.perPage) && position.perPage >= 1 && position.perPage <= API_MAX_PER_PAGE && Number.isInteger(position.skip) && position.skip >= 0) {
|
|
286
320
|
return position;
|
|
287
321
|
}
|
|
288
|
-
warn(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
322
|
+
warn(
|
|
323
|
+
"Ignoring an unreadable pagination cursor; starting from the first page."
|
|
324
|
+
);
|
|
325
|
+
return { cursor: void 0, skip: 0 };
|
|
326
|
+
}
|
|
327
|
+
function consumeCursorPage(page, position, limit2 = Number.POSITIVE_INFINITY) {
|
|
328
|
+
const consumed = consumePage(
|
|
329
|
+
{ data: page.data, nextCursor: page.nextCursor || void 0 },
|
|
330
|
+
{ cursor: position.cursor, offset: position.skip },
|
|
331
|
+
limit2
|
|
332
|
+
);
|
|
293
333
|
return {
|
|
294
334
|
...page,
|
|
295
335
|
data: consumed.data,
|
|
296
|
-
nextCursor: consumed.position ? encodePageCursor(consumed.position.cursor, consumed.position.offset) :
|
|
336
|
+
nextCursor: consumed.position ? encodePageCursor(consumed.position.cursor, consumed.position.offset) : void 0
|
|
297
337
|
};
|
|
298
338
|
}
|
|
299
339
|
function serverCursorPageSize(cursor) {
|
|
300
340
|
const value = Number(PAGE_NUMBER_CURSOR_RE.exec(cursor)?.[1]);
|
|
301
|
-
return Number.isInteger(value) && value >= 1 && value <= API_MAX_PER_PAGE ? value :
|
|
341
|
+
return Number.isInteger(value) && value >= 1 && value <= API_MAX_PER_PAGE ? value : void 0;
|
|
302
342
|
}
|
|
303
|
-
function pageSizeForLimit(
|
|
304
|
-
if (
|
|
305
|
-
return Math.max(1,
|
|
343
|
+
function pageSizeForLimit(limit2) {
|
|
344
|
+
if (limit2 <= API_MAX_PER_PAGE) {
|
|
345
|
+
return Math.max(1, limit2);
|
|
306
346
|
}
|
|
307
|
-
return Math.ceil(
|
|
347
|
+
return Math.ceil(limit2 / Math.ceil(limit2 / API_MAX_PER_PAGE));
|
|
308
348
|
}
|
|
309
349
|
async function paginate(options, fetchPage) {
|
|
310
|
-
const { limit, maxPages, warn = () => {
|
|
350
|
+
const { limit: limit2, maxPages, warn = () => {
|
|
351
|
+
} } = options;
|
|
311
352
|
const position = decodePageCursor(options.cursor, warn);
|
|
312
|
-
const perPage = position.perPage ?? pageSizeForLimit(
|
|
313
|
-
const result = await paginateUpTo(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
353
|
+
const perPage = position.perPage ?? pageSizeForLimit(limit2 + position.skip);
|
|
354
|
+
const result = await paginateUpTo(
|
|
355
|
+
async (cursor) => {
|
|
356
|
+
const page = await fetchPage(perPage, cursor);
|
|
357
|
+
return { data: page.data, nextCursor: page.nextCursor || void 0 };
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
limit: limit2,
|
|
361
|
+
maxPages,
|
|
362
|
+
resume: {
|
|
363
|
+
cursor: position.cursor,
|
|
364
|
+
offset: position.skip,
|
|
365
|
+
requestBudget: limit2 + position.skip
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"paginate"
|
|
369
|
+
);
|
|
325
370
|
return {
|
|
326
371
|
data: result.data,
|
|
327
372
|
...result.continuation ? {
|
|
328
|
-
nextCursor: encodePageCursor(
|
|
373
|
+
nextCursor: encodePageCursor(
|
|
374
|
+
result.continuation.cursor,
|
|
375
|
+
result.continuation.offset
|
|
376
|
+
)
|
|
329
377
|
} : {},
|
|
330
378
|
stoppedAtPageCap: result.stopReason === "maxPages"
|
|
331
379
|
};
|
|
332
380
|
}
|
|
333
381
|
async function fetchAllPages(fetchPage, parse, maxPages) {
|
|
334
382
|
const items = [];
|
|
335
|
-
const seen = new Set;
|
|
383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
336
384
|
let cursor;
|
|
337
|
-
for (let page = 0;page < maxPages; page += 1) {
|
|
385
|
+
for (let page = 0; page < maxPages; page += 1) {
|
|
338
386
|
const { data, nextCursor } = await fetchPage(cursor);
|
|
339
387
|
const rows = parse(data);
|
|
340
388
|
if (!rows) {
|
|
@@ -354,25 +402,25 @@ async function fetchAllPages(fetchPage, parse, maxPages) {
|
|
|
354
402
|
}
|
|
355
403
|
|
|
356
404
|
// src/api-client/retry.ts
|
|
357
|
-
var IDEMPOTENT_METHODS = new Set([
|
|
405
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
358
406
|
"GET",
|
|
359
407
|
"HEAD",
|
|
360
408
|
"OPTIONS"
|
|
361
409
|
]);
|
|
362
|
-
var RETRYABLE_STATUSES = new Map([
|
|
410
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Map([
|
|
363
411
|
[429, "any"],
|
|
364
412
|
[500, "idempotent"],
|
|
365
413
|
[502, "idempotent"],
|
|
366
414
|
[503, "idempotent"],
|
|
367
415
|
[504, "idempotent"]
|
|
368
416
|
]);
|
|
369
|
-
var DATE_RESOLUTION_MS =
|
|
417
|
+
var DATE_RESOLUTION_MS = 1e3;
|
|
370
418
|
function isIdempotentRequest(method, idempotent = false) {
|
|
371
419
|
return idempotent || IDEMPOTENT_METHODS.has(method.toUpperCase());
|
|
372
420
|
}
|
|
373
421
|
function isRetryable(method, status, idempotent = false) {
|
|
374
|
-
const replays = status ===
|
|
375
|
-
if (replays ===
|
|
422
|
+
const replays = status === void 0 ? "idempotent" : RETRYABLE_STATUSES.get(status);
|
|
423
|
+
if (replays === void 0) {
|
|
376
424
|
return false;
|
|
377
425
|
}
|
|
378
426
|
return replays === "any" || isIdempotentRequest(method, idempotent);
|
|
@@ -381,19 +429,19 @@ function parseRetryDelayMs(status, headers, now = Date.now()) {
|
|
|
381
429
|
const retryAfter = headers.get("retry-after")?.trim();
|
|
382
430
|
if (retryAfter) {
|
|
383
431
|
if (/^\d+$/.test(retryAfter)) {
|
|
384
|
-
return Number(retryAfter) *
|
|
432
|
+
return Number(retryAfter) * 1e3;
|
|
385
433
|
}
|
|
386
434
|
const date = Date.parse(retryAfter);
|
|
387
|
-
return Number.isNaN(date) ?
|
|
435
|
+
return Number.isNaN(date) ? void 0 : delayUntil(date, headers, now);
|
|
388
436
|
}
|
|
389
437
|
if (status !== 429 || !isWindowLimited(headers)) {
|
|
390
|
-
return;
|
|
438
|
+
return void 0;
|
|
391
439
|
}
|
|
392
440
|
const reset = Number(headers.get("x-sentry-rate-limit-reset"));
|
|
393
441
|
if (!Number.isFinite(reset) || reset <= 0) {
|
|
394
|
-
return;
|
|
442
|
+
return void 0;
|
|
395
443
|
}
|
|
396
|
-
return delayUntil(reset *
|
|
444
|
+
return delayUntil(reset * 1e3, headers, now);
|
|
397
445
|
}
|
|
398
446
|
function nextRetryDelayMs({
|
|
399
447
|
policy,
|
|
@@ -406,19 +454,19 @@ function nextRetryDelayMs({
|
|
|
406
454
|
random = Math.random
|
|
407
455
|
}) {
|
|
408
456
|
if (attempt >= policy.maxRetries || !isRetryable(method, status, idempotent)) {
|
|
409
|
-
return;
|
|
457
|
+
return void 0;
|
|
410
458
|
}
|
|
411
459
|
let delayMs;
|
|
412
|
-
if (serverDelayMs !==
|
|
460
|
+
if (serverDelayMs !== void 0) {
|
|
413
461
|
if (serverDelayMs > policy.maxDelayMs) {
|
|
414
|
-
return;
|
|
462
|
+
return void 0;
|
|
415
463
|
}
|
|
416
464
|
delayMs = serverDelayMs + random() * (policy.baseDelayMs / 2);
|
|
417
465
|
} else {
|
|
418
466
|
const step = Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
|
|
419
467
|
delayMs = step / 2 + random() * (step / 2);
|
|
420
468
|
}
|
|
421
|
-
return waitedMs + delayMs > policy.maxTotalDelayMs ?
|
|
469
|
+
return waitedMs + delayMs > policy.maxTotalDelayMs ? void 0 : delayMs;
|
|
422
470
|
}
|
|
423
471
|
function sleep(ms, signal) {
|
|
424
472
|
return new Promise((resolve, reject) => {
|
|
@@ -453,9 +501,9 @@ function isWindowLimited(headers) {
|
|
|
453
501
|
// src/api-client/response.ts
|
|
454
502
|
function readDetail(json) {
|
|
455
503
|
if (json === null || typeof json !== "object" || !("detail" in json)) {
|
|
456
|
-
return;
|
|
504
|
+
return void 0;
|
|
457
505
|
}
|
|
458
|
-
return typeof json.detail === "string" ? json.detail :
|
|
506
|
+
return typeof json.detail === "string" ? json.detail : void 0;
|
|
459
507
|
}
|
|
460
508
|
function looksLikeHtml(text) {
|
|
461
509
|
return text.includes("<!DOCTYPE") || text.includes("<html");
|
|
@@ -466,7 +514,7 @@ async function readErrorResponse(response) {
|
|
|
466
514
|
try {
|
|
467
515
|
text = await response.text();
|
|
468
516
|
} catch {
|
|
469
|
-
return { text:
|
|
517
|
+
return { text: void 0, isHtml: false, retryAfterMs };
|
|
470
518
|
}
|
|
471
519
|
try {
|
|
472
520
|
const json = JSON.parse(text);
|
|
@@ -494,24 +542,31 @@ async function readJsonResponse(response) {
|
|
|
494
542
|
if (!contentType?.includes("application/json")) {
|
|
495
543
|
const text = await response.text();
|
|
496
544
|
if (contentType?.includes("text/html") || looksLikeHtml(text)) {
|
|
497
|
-
throw new Error(
|
|
545
|
+
throw new Error(
|
|
546
|
+
`Expected JSON response but received HTML (${response.status} ${response.statusText}). This may indicate you're not authenticated, the URL is incorrect, or there's a server issue.`
|
|
547
|
+
);
|
|
498
548
|
}
|
|
499
|
-
throw new Error(
|
|
549
|
+
throw new Error(
|
|
550
|
+
`Expected JSON response but received ${contentType || "unknown content type"} (${response.status} ${response.statusText})`
|
|
551
|
+
);
|
|
500
552
|
}
|
|
501
553
|
try {
|
|
502
554
|
return await response.json();
|
|
503
555
|
} catch (error) {
|
|
504
|
-
throw new Error(
|
|
556
|
+
throw new Error(
|
|
557
|
+
`Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`
|
|
558
|
+
);
|
|
505
559
|
}
|
|
506
560
|
}
|
|
507
561
|
|
|
508
562
|
// src/api-client/validate.ts
|
|
509
563
|
var MAX_LISTED_ISSUES = 3;
|
|
510
564
|
function issuePath(issue) {
|
|
511
|
-
return (issue.path ?? []).map(
|
|
565
|
+
return (issue.path ?? []).map(
|
|
566
|
+
(segment) => String(typeof segment === "object" ? segment.key : segment)
|
|
567
|
+
).join(".");
|
|
512
568
|
}
|
|
513
|
-
|
|
514
|
-
class ApiResponseValidationError extends Error {
|
|
569
|
+
var ApiResponseValidationError = class extends Error {
|
|
515
570
|
issues;
|
|
516
571
|
constructor(issues) {
|
|
517
572
|
const listed = issues.slice(0, MAX_LISTED_ISSUES).map((issue) => {
|
|
@@ -523,7 +578,7 @@ class ApiResponseValidationError extends Error {
|
|
|
523
578
|
this.name = "ApiResponseValidationError";
|
|
524
579
|
this.issues = issues;
|
|
525
580
|
}
|
|
526
|
-
}
|
|
581
|
+
};
|
|
527
582
|
function validateResponse(schema, data) {
|
|
528
583
|
const result = schema["~standard"].validate(data);
|
|
529
584
|
if (result instanceof Promise) {
|
|
@@ -548,7 +603,7 @@ var NETWORK_ERROR_MESSAGES = {
|
|
|
548
603
|
ECONNRESET: "Connection reset. Try again in a moment."
|
|
549
604
|
};
|
|
550
605
|
var API_PREFIX = "/api/0";
|
|
551
|
-
var CONFIGURATION_ERROR_CODES = new Set([
|
|
606
|
+
var CONFIGURATION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
552
607
|
"ENOTFOUND",
|
|
553
608
|
"EAI_AGAIN",
|
|
554
609
|
"ECONNREFUSED",
|
|
@@ -560,7 +615,9 @@ function networkError(url, error) {
|
|
|
560
615
|
rootCause = rootCause.cause;
|
|
561
616
|
}
|
|
562
617
|
const errorMessage = rootCause instanceof Error ? rootCause.message : String(rootCause);
|
|
563
|
-
const errorCode = Object.keys(NETWORK_ERROR_MESSAGES).find(
|
|
618
|
+
const errorCode = Object.keys(NETWORK_ERROR_MESSAGES).find(
|
|
619
|
+
(code) => errorMessage.includes(code)
|
|
620
|
+
);
|
|
564
621
|
const friendlyMessage = `Unable to connect to ${url} - ${errorCode ? NETWORK_ERROR_MESSAGES[errorCode] : errorMessage}`;
|
|
565
622
|
if (errorCode && CONFIGURATION_ERROR_CODES.has(errorCode) || errorMessage.includes("Connect Timeout Error")) {
|
|
566
623
|
return new ConfigurationError(friendlyMessage, { cause: error });
|
|
@@ -570,14 +627,20 @@ function networkError(url, error) {
|
|
|
570
627
|
function errorFromResponse(logger, response, body, logExtra) {
|
|
571
628
|
const { retryAfterMs } = body;
|
|
572
629
|
const errorText = body.text ?? "";
|
|
573
|
-
const bodyPreview = errorText.length > 256 ? `${errorText.slice(0, 253)}
|
|
574
|
-
if (body.parseError !==
|
|
630
|
+
const bodyPreview = errorText.length > 256 ? `${errorText.slice(0, 253)}\u2026` : errorText;
|
|
631
|
+
if (body.parseError !== void 0) {
|
|
575
632
|
if (body.isHtml) {
|
|
576
633
|
logger.warn("Received HTML error page instead of JSON", {
|
|
577
634
|
...logExtra,
|
|
578
635
|
parseErrorMessage: body.parseError
|
|
579
636
|
});
|
|
580
|
-
return createApiError(
|
|
637
|
+
return createApiError(
|
|
638
|
+
`Server error: Received HTML instead of JSON (${response.status} ${response.statusText}). This may indicate an invalid URL or server issue.`,
|
|
639
|
+
response.status,
|
|
640
|
+
errorText,
|
|
641
|
+
void 0,
|
|
642
|
+
retryAfterMs
|
|
643
|
+
);
|
|
581
644
|
}
|
|
582
645
|
logger.warn("Failed to parse JSON error response", {
|
|
583
646
|
...logExtra,
|
|
@@ -585,8 +648,14 @@ function errorFromResponse(logger, response, body, logExtra) {
|
|
|
585
648
|
parseErrorMessage: body.parseError
|
|
586
649
|
});
|
|
587
650
|
} else if (body.json) {
|
|
588
|
-
if (body.detail !==
|
|
589
|
-
return createApiError(
|
|
651
|
+
if (body.detail !== void 0) {
|
|
652
|
+
return createApiError(
|
|
653
|
+
body.detail,
|
|
654
|
+
response.status,
|
|
655
|
+
body.detail,
|
|
656
|
+
body.json,
|
|
657
|
+
retryAfterMs
|
|
658
|
+
);
|
|
590
659
|
}
|
|
591
660
|
logger.warn("Failed to parse validated API error response", {
|
|
592
661
|
...logExtra,
|
|
@@ -594,8 +663,14 @@ function errorFromResponse(logger, response, body, logExtra) {
|
|
|
594
663
|
validationErrorMessage: "Error response has no string detail"
|
|
595
664
|
});
|
|
596
665
|
}
|
|
597
|
-
return createApiError(
|
|
598
|
-
${
|
|
666
|
+
return createApiError(
|
|
667
|
+
`API request failed: ${response.statusText}
|
|
668
|
+
${errorText}`,
|
|
669
|
+
response.status,
|
|
670
|
+
errorText,
|
|
671
|
+
void 0,
|
|
672
|
+
retryAfterMs
|
|
673
|
+
);
|
|
599
674
|
}
|
|
600
675
|
function createApiClientCore({
|
|
601
676
|
transport,
|
|
@@ -609,7 +684,9 @@ function createApiClientCore({
|
|
|
609
684
|
...headers()
|
|
610
685
|
};
|
|
611
686
|
if (typeof globalThis.fetch === "undefined") {
|
|
612
|
-
throw new ConfigurationError(
|
|
687
|
+
throw new ConfigurationError(
|
|
688
|
+
"fetch is not available. Please use Node.js >= 18 or ensure fetch is available in your environment."
|
|
689
|
+
);
|
|
613
690
|
}
|
|
614
691
|
let response;
|
|
615
692
|
try {
|
|
@@ -626,12 +703,17 @@ function createApiClientCore({
|
|
|
626
703
|
if (response.ok || allowStatuses?.includes(response.status)) {
|
|
627
704
|
return response;
|
|
628
705
|
}
|
|
629
|
-
throw errorFromResponse(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
706
|
+
throw errorFromResponse(
|
|
707
|
+
logger,
|
|
708
|
+
response,
|
|
709
|
+
await readErrorResponse(response),
|
|
710
|
+
{
|
|
711
|
+
status: response.status,
|
|
712
|
+
statusText: response.statusText,
|
|
713
|
+
host: new URL(origin).host,
|
|
714
|
+
path
|
|
715
|
+
}
|
|
716
|
+
);
|
|
635
717
|
}
|
|
636
718
|
return {
|
|
637
719
|
request,
|
|
@@ -646,9 +728,15 @@ function createApiClientCore({
|
|
|
646
728
|
}
|
|
647
729
|
const url = new URL(sdkRequest.url);
|
|
648
730
|
if (url.origin !== base || url.pathname !== API_PREFIX.concat(expectedPath) || sdkRequest.method !== "GET") {
|
|
649
|
-
throw new ConfigurationError(
|
|
731
|
+
throw new ConfigurationError(
|
|
732
|
+
"Unexpected SDK request target or method"
|
|
733
|
+
);
|
|
650
734
|
}
|
|
651
|
-
const response = await request(
|
|
735
|
+
const response = await request(
|
|
736
|
+
origin,
|
|
737
|
+
`${url.pathname.slice(API_PREFIX.length)}${url.search}`,
|
|
738
|
+
{ method: "GET", signal: sdkRequest.signal }
|
|
739
|
+
);
|
|
652
740
|
const contentType = response.headers.get("content-type");
|
|
653
741
|
if (!contentType?.includes("application/json")) {
|
|
654
742
|
await readJsonResponse(response);
|
|
@@ -659,15 +747,17 @@ function createApiClientCore({
|
|
|
659
747
|
}
|
|
660
748
|
};
|
|
661
749
|
}
|
|
662
|
-
async function listPage(fetchPage, schema,
|
|
663
|
-
if (!Number.isSafeInteger(
|
|
664
|
-
throw new ApiValidationError(
|
|
750
|
+
async function listPage(fetchPage, schema, limit2, initialCursor) {
|
|
751
|
+
if (!Number.isSafeInteger(limit2) || limit2 < 1 || limit2 > API_MAX_PER_PAGE * MAX_LIST_PAGES) {
|
|
752
|
+
throw new ApiValidationError(
|
|
753
|
+
`limit must be an integer between 1 and ${API_MAX_PER_PAGE * MAX_LIST_PAGES}`
|
|
754
|
+
);
|
|
665
755
|
}
|
|
666
756
|
const items = [];
|
|
667
|
-
const seenCursors = new Set;
|
|
668
|
-
let cursor = initialCursor ??
|
|
669
|
-
for (let page = 0;page < MAX_LIST_PAGES; page += 1) {
|
|
670
|
-
const perPage = Math.min(API_MAX_PER_PAGE,
|
|
757
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
758
|
+
let cursor = initialCursor ?? void 0;
|
|
759
|
+
for (let page = 0; page < MAX_LIST_PAGES; page += 1) {
|
|
760
|
+
const perPage = Math.min(API_MAX_PER_PAGE, limit2 - items.length);
|
|
671
761
|
const { data, response } = await fetchPage(perPage, cursor);
|
|
672
762
|
const rows = validateResponse(schema, data);
|
|
673
763
|
if (rows.length > perPage) {
|
|
@@ -675,7 +765,7 @@ async function listPage(fetchPage, schema, limit, initialCursor) {
|
|
|
675
765
|
}
|
|
676
766
|
items.push(...rows);
|
|
677
767
|
const nextCursor = nextPageCursor(response);
|
|
678
|
-
if (items.length >=
|
|
768
|
+
if (items.length >= limit2 || !nextCursor) {
|
|
679
769
|
return { items, nextCursor };
|
|
680
770
|
}
|
|
681
771
|
if (seenCursors.has(nextCursor) || nextCursor === cursor) {
|
|
@@ -686,14 +776,19 @@ async function listPage(fetchPage, schema, limit, initialCursor) {
|
|
|
686
776
|
}
|
|
687
777
|
throw new Error("API pagination exceeded its page limit");
|
|
688
778
|
}
|
|
779
|
+
|
|
689
780
|
// src/api-client/idempotent-operations.ts
|
|
690
781
|
function selectIdempotentWrites(operations) {
|
|
691
|
-
return operations.filter((op) => op.idempotent && !isIdempotentRequest(op.method)).map((op) => ({ method: op.method, path: op.path })).sort(
|
|
782
|
+
return operations.filter((op) => op.idempotent && !isIdempotentRequest(op.method)).map((op) => ({ method: op.method, path: op.path })).sort(
|
|
783
|
+
(a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method)
|
|
784
|
+
);
|
|
692
785
|
}
|
|
693
786
|
var TEMPLATE_SEGMENT = /^\{[^/]+\}$/;
|
|
694
787
|
var REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
|
|
695
788
|
function toMatcher({ method, path }) {
|
|
696
|
-
const source = path.split("/").map(
|
|
789
|
+
const source = path.split("/").map(
|
|
790
|
+
(segment) => TEMPLATE_SEGMENT.test(segment) ? "[^/]+" : segment.replace(REGEXP_SPECIAL, "\\$&")
|
|
791
|
+
).join("/");
|
|
697
792
|
return { method: method.toUpperCase(), pattern: new RegExp(`^${source}$`) };
|
|
698
793
|
}
|
|
699
794
|
function createIdempotentOperationMatcher(operations) {
|
|
@@ -711,25 +806,39 @@ function createIdempotentOperationMatcher(operations) {
|
|
|
711
806
|
const normalized = apiPath2.endsWith("/") ? apiPath2 : `${apiPath2}/`;
|
|
712
807
|
const upper = method.toUpperCase();
|
|
713
808
|
matchers ??= operations().map(toMatcher);
|
|
714
|
-
return matchers.some(
|
|
809
|
+
return matchers.some(
|
|
810
|
+
(matcher) => matcher.method === upper && matcher.pattern.test(normalized)
|
|
811
|
+
);
|
|
715
812
|
};
|
|
716
813
|
}
|
|
814
|
+
|
|
717
815
|
// src/api-client/response-cache/invalidation.ts
|
|
718
816
|
var API_V0_SEGMENT = "/api/0/";
|
|
719
817
|
var TRAILING_SLASH_RE = /\/$/;
|
|
720
818
|
var SKIP_INVALIDATION_PATTERNS = [
|
|
819
|
+
// Sourcemap chunk upload + bundle assemble. Both are write-only in the
|
|
820
|
+
// sense that no GET endpoint reads cacheable state under these paths.
|
|
721
821
|
/\/chunk-upload\//,
|
|
722
822
|
/\/artifactbundle\/assemble\//
|
|
723
823
|
];
|
|
724
824
|
var CROSS_ENDPOINT_RULES = [
|
|
825
|
+
// `POST teams/{org}/{team}/projects/` (create project in team) also
|
|
826
|
+
// invalidates the org project list at `organizations/{org}/projects/`.
|
|
725
827
|
{
|
|
726
828
|
match: /^teams\/([^/]+)\/[^/]+\/projects\/?$/,
|
|
727
829
|
extra: ([, org]) => [`organizations/${org}/projects/`]
|
|
728
830
|
},
|
|
831
|
+
// `DELETE projects/{org}/{project}/` also invalidates the org project list.
|
|
729
832
|
{
|
|
730
833
|
match: /^projects\/([^/]+)\/[^/]+\/?$/,
|
|
731
834
|
extra: ([, org]) => [`organizations/${org}/projects/`]
|
|
732
835
|
},
|
|
836
|
+
// Org-scoped issue mutations at `organizations/{org}/issues/{id}/`
|
|
837
|
+
// also affect the legacy global endpoint at `issues/{id}/`, which
|
|
838
|
+
// `getIssue()` hits under the control-silo base URL (potentially a
|
|
839
|
+
// DIFFERENT origin than the org's region URL). Must clear the
|
|
840
|
+
// legacy cache too, or subsequent `getIssue()` calls serve stale
|
|
841
|
+
// data.
|
|
733
842
|
{
|
|
734
843
|
match: /^organizations\/[^/]+\/issues\/([^/]+)\/?$/,
|
|
735
844
|
extraAbsolute: ([, issueId], { apiBaseUrl }) => [
|
|
@@ -756,7 +865,7 @@ function computeInvalidationPrefixes(fullUrl, apiBaseUrl) {
|
|
|
756
865
|
if (relPath === "") {
|
|
757
866
|
return [];
|
|
758
867
|
}
|
|
759
|
-
const prefixes = new Set;
|
|
868
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
760
869
|
for (const segments of ancestorSegments(relPath)) {
|
|
761
870
|
prefixes.add(`${base}${segments}`);
|
|
762
871
|
}
|
|
@@ -786,23 +895,26 @@ function* ancestorSegments(relPath) {
|
|
|
786
895
|
}
|
|
787
896
|
const parts = trimmed.split("/");
|
|
788
897
|
const floor = parts.length > 2 ? 2 : 1;
|
|
789
|
-
for (let i = parts.length;i >= floor; i--) {
|
|
898
|
+
for (let i = parts.length; i >= floor; i--) {
|
|
790
899
|
yield `${parts.slice(0, i).join("/")}/`;
|
|
791
900
|
}
|
|
792
901
|
}
|
|
902
|
+
|
|
793
903
|
// src/api-client/response-cache/keys.ts
|
|
794
|
-
var encoder = new TextEncoder;
|
|
904
|
+
var encoder = new TextEncoder();
|
|
795
905
|
function normalizeUrl(method, url) {
|
|
796
906
|
const parsed = new URL(url);
|
|
797
|
-
const sortedParams = new URLSearchParams(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
907
|
+
const sortedParams = new URLSearchParams(
|
|
908
|
+
[...parsed.searchParams.entries()].sort(([a], [b]) => {
|
|
909
|
+
if (a < b) {
|
|
910
|
+
return -1;
|
|
911
|
+
}
|
|
912
|
+
if (a > b) {
|
|
913
|
+
return 1;
|
|
914
|
+
}
|
|
915
|
+
return 0;
|
|
916
|
+
})
|
|
917
|
+
);
|
|
806
918
|
parsed.search = sortedParams.toString() ? `?${sortedParams.toString()}` : "";
|
|
807
919
|
return `${method.toUpperCase()}|${parsed.toString()}`;
|
|
808
920
|
}
|
|
@@ -811,15 +923,29 @@ async function buildCacheKey(method, url, identity) {
|
|
|
811
923
|
throw new TypeError("A cache key needs a non-empty identity");
|
|
812
924
|
}
|
|
813
925
|
const normalized = normalizeUrl(method, url);
|
|
814
|
-
const key = await crypto.subtle.importKey(
|
|
815
|
-
|
|
816
|
-
|
|
926
|
+
const key = await crypto.subtle.importKey(
|
|
927
|
+
"raw",
|
|
928
|
+
encoder.encode(identity),
|
|
929
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
930
|
+
false,
|
|
931
|
+
["sign"]
|
|
932
|
+
);
|
|
933
|
+
const signature = await crypto.subtle.sign(
|
|
934
|
+
"HMAC",
|
|
935
|
+
key,
|
|
936
|
+
encoder.encode(normalized)
|
|
937
|
+
);
|
|
938
|
+
return Array.from(
|
|
939
|
+
new Uint8Array(signature),
|
|
940
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
941
|
+
).join("");
|
|
817
942
|
}
|
|
943
|
+
|
|
818
944
|
// src/api-client/response-cache/memory-store.ts
|
|
819
945
|
var MIB = 1024 * 1024;
|
|
820
946
|
var ENTRY_OVERHEAD_BYTES = 1024;
|
|
821
947
|
function limit(name, value, fallback) {
|
|
822
|
-
if (value ===
|
|
948
|
+
if (value === void 0) {
|
|
823
949
|
return fallback;
|
|
824
950
|
}
|
|
825
951
|
if (!(Number.isFinite(value) && value > 0)) {
|
|
@@ -834,7 +960,7 @@ function createMemoryStore(options = {}) {
|
|
|
834
960
|
const maxBytes = limit("maxBytes", options.maxBytes, 24 * MIB);
|
|
835
961
|
const maxEntryBytes = limit("maxEntryBytes", options.maxEntryBytes, MIB);
|
|
836
962
|
const maxRawBytes = limit("maxRawBytes", options.maxRawBytes, 32 * MIB);
|
|
837
|
-
const slots = new Map;
|
|
963
|
+
const slots = /* @__PURE__ */ new Map();
|
|
838
964
|
let bytes = 0;
|
|
839
965
|
function remove(key) {
|
|
840
966
|
const slot = slots.get(key);
|
|
@@ -890,24 +1016,32 @@ function createMemoryStore(options = {}) {
|
|
|
890
1016
|
}
|
|
891
1017
|
};
|
|
892
1018
|
}
|
|
1019
|
+
|
|
893
1020
|
// src/api-client/response-cache/policy.ts
|
|
894
1021
|
import CachePolicy from "http-cache-semantics";
|
|
895
1022
|
|
|
896
1023
|
// src/api-client/response-cache/tiers.ts
|
|
897
1024
|
var FALLBACK_TTL_MS = {
|
|
898
|
-
stable: 5 * 60 *
|
|
899
|
-
volatile: 60 *
|
|
1025
|
+
stable: 5 * 60 * 1e3,
|
|
1026
|
+
volatile: 60 * 1e3,
|
|
900
1027
|
"no-cache": 0
|
|
901
1028
|
};
|
|
902
1029
|
var URL_TIER_PATTERNS = [
|
|
1030
|
+
// Polling endpoints where state changes rapidly
|
|
903
1031
|
["no-cache", [/\/(?:autofix|root-cause)\//]],
|
|
904
1032
|
[
|
|
905
1033
|
"volatile",
|
|
906
1034
|
[
|
|
1035
|
+
// Issue endpoints: lists, detail views, and issue events such as
|
|
1036
|
+
// `/issues/{id}/events/latest/`
|
|
907
1037
|
/\/issues\//,
|
|
908
1038
|
/[?&]dataset=(?:logs|spans)/,
|
|
909
1039
|
/\/trace-logs\//,
|
|
1040
|
+
// Full traces gain spans as they arrive
|
|
910
1041
|
/\/trace\/[0-9a-f]{32}\//,
|
|
1042
|
+
// Project event details embed user reports (and `nextEventID`/
|
|
1043
|
+
// `previousEventID` unless collapsed), and reprocessing can change them
|
|
1044
|
+
// for the same ID
|
|
911
1045
|
/\/projects\/[^/]+\/[^/]+\/events\/[^/?]+\/?(?:\?|$)/
|
|
912
1046
|
]
|
|
913
1047
|
]
|
|
@@ -922,13 +1056,13 @@ function classifyUrl(url) {
|
|
|
922
1056
|
}
|
|
923
1057
|
|
|
924
1058
|
// src/api-client/response-cache/policy.ts
|
|
925
|
-
var REVALIDATION_WINDOW_MS = 24 * 60 * 60 *
|
|
1059
|
+
var REVALIDATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
926
1060
|
var POLICY_OPTIONS = {
|
|
927
1061
|
shared: false,
|
|
928
1062
|
cacheHeuristic: 0.1
|
|
929
1063
|
};
|
|
930
1064
|
var PRESERVED_HEADERS = ["link"];
|
|
931
|
-
var encoder2 = new TextEncoder;
|
|
1065
|
+
var encoder2 = new TextEncoder();
|
|
932
1066
|
function pickHeaders(headers) {
|
|
933
1067
|
const result = {};
|
|
934
1068
|
for (const name of PRESERVED_HEADERS) {
|
|
@@ -948,7 +1082,10 @@ async function policyRequestHeaders(headers) {
|
|
|
948
1082
|
}
|
|
949
1083
|
async function sha256Hex(value) {
|
|
950
1084
|
const digest = await crypto.subtle.digest("SHA-256", encoder2.encode(value));
|
|
951
|
-
return Array.from(
|
|
1085
|
+
return Array.from(
|
|
1086
|
+
new Uint8Array(digest),
|
|
1087
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
1088
|
+
).join("");
|
|
952
1089
|
}
|
|
953
1090
|
function headersToObject(headers) {
|
|
954
1091
|
return Object.fromEntries(headers.entries());
|
|
@@ -968,12 +1105,12 @@ function isEntryFresh(policy, entry, request) {
|
|
|
968
1105
|
}
|
|
969
1106
|
function retentionMs(policy, tier) {
|
|
970
1107
|
const freshMs = hasServerCacheDirectives(policy) ? policy.timeToLive() : FALLBACK_TTL_MS[tier];
|
|
971
|
-
return policy.responseHeaders().etag ===
|
|
1108
|
+
return policy.responseHeaders().etag === void 0 ? freshMs : freshMs + REVALIDATION_WINDOW_MS;
|
|
972
1109
|
}
|
|
973
1110
|
function buildResponseHeaders(policy, entry) {
|
|
974
1111
|
const result = {};
|
|
975
1112
|
for (const [name, value] of Object.entries(policy.responseHeaders())) {
|
|
976
|
-
if (value !==
|
|
1113
|
+
if (value !== void 0) {
|
|
977
1114
|
result[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
978
1115
|
}
|
|
979
1116
|
}
|
|
@@ -1030,7 +1167,9 @@ async function lookupResponse(options) {
|
|
|
1030
1167
|
if (!entry) {
|
|
1031
1168
|
return;
|
|
1032
1169
|
}
|
|
1033
|
-
const policy = CachePolicy.fromObject(
|
|
1170
|
+
const policy = CachePolicy.fromObject(
|
|
1171
|
+
entry.policy
|
|
1172
|
+
);
|
|
1034
1173
|
const request = {
|
|
1035
1174
|
url,
|
|
1036
1175
|
method: "GET",
|
|
@@ -1059,11 +1198,15 @@ async function storeResponse(options) {
|
|
|
1059
1198
|
if (!response.ok || tier === "no-cache") {
|
|
1060
1199
|
return 0;
|
|
1061
1200
|
}
|
|
1062
|
-
const policy = new CachePolicy(
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1201
|
+
const policy = new CachePolicy(
|
|
1202
|
+
{
|
|
1203
|
+
url,
|
|
1204
|
+
method: "GET",
|
|
1205
|
+
headers: await policyRequestHeaders(requestHeaders)
|
|
1206
|
+
},
|
|
1207
|
+
{ status: response.status, headers: headersToObject(response.headers) },
|
|
1208
|
+
POLICY_OPTIONS
|
|
1209
|
+
);
|
|
1067
1210
|
if (!policy.storable()) {
|
|
1068
1211
|
return 0;
|
|
1069
1212
|
}
|
|
@@ -1071,22 +1214,27 @@ async function storeResponse(options) {
|
|
|
1071
1214
|
JSON.parse(text);
|
|
1072
1215
|
const createdAt = Date.now();
|
|
1073
1216
|
const body = await codec.encode(encoder2.encode(text));
|
|
1074
|
-
await store.set(
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1217
|
+
await store.set(
|
|
1218
|
+
key,
|
|
1219
|
+
{
|
|
1220
|
+
url,
|
|
1221
|
+
identity,
|
|
1222
|
+
tier,
|
|
1223
|
+
status: response.status,
|
|
1224
|
+
headers: pickHeaders(response.headers),
|
|
1225
|
+
body,
|
|
1226
|
+
policy: policy.toObject(),
|
|
1227
|
+
createdAt
|
|
1228
|
+
},
|
|
1229
|
+
retentionMs(policy, tier)
|
|
1230
|
+
);
|
|
1084
1231
|
return body.bytes.byteLength;
|
|
1085
1232
|
}
|
|
1233
|
+
|
|
1086
1234
|
// src/api-client/time-params.ts
|
|
1087
1235
|
function normalizeStatsPeriod(statsPeriod) {
|
|
1088
1236
|
const normalized = statsPeriod?.trim();
|
|
1089
|
-
return normalized ||
|
|
1237
|
+
return normalized || void 0;
|
|
1090
1238
|
}
|
|
1091
1239
|
function defaultStatsPeriod({
|
|
1092
1240
|
statsPeriod,
|
|
@@ -1099,7 +1247,9 @@ function defaultStatsPeriod({
|
|
|
1099
1247
|
function parseStatsPeriod(statsPeriod) {
|
|
1100
1248
|
const match = /^(\d+)([smhdw])$/.exec(statsPeriod);
|
|
1101
1249
|
if (!match?.[2]) {
|
|
1102
|
-
throw new ApiValidationError(
|
|
1250
|
+
throw new ApiValidationError(
|
|
1251
|
+
"statsPeriod must use a supported relative time format, such as `24h`, `7d`, or `2w`."
|
|
1252
|
+
);
|
|
1103
1253
|
}
|
|
1104
1254
|
return {
|
|
1105
1255
|
amount: Number(match[1]),
|
|
@@ -1108,10 +1258,14 @@ function parseStatsPeriod(statsPeriod) {
|
|
|
1108
1258
|
}
|
|
1109
1259
|
function applyTimeParams(queryParams, statsPeriod, start, end) {
|
|
1110
1260
|
if (statsPeriod && (start || end)) {
|
|
1111
|
-
throw new ApiValidationError(
|
|
1261
|
+
throw new ApiValidationError(
|
|
1262
|
+
"Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time."
|
|
1263
|
+
);
|
|
1112
1264
|
}
|
|
1113
1265
|
if (start && !end || !start && end) {
|
|
1114
|
-
throw new ApiValidationError(
|
|
1266
|
+
throw new ApiValidationError(
|
|
1267
|
+
"Both start and end parameters must be provided together for absolute time ranges."
|
|
1268
|
+
);
|
|
1115
1269
|
}
|
|
1116
1270
|
if (statsPeriod) {
|
|
1117
1271
|
queryParams.set("statsPeriod", statsPeriod);
|
|
@@ -1122,10 +1276,14 @@ function applyTimeParams(queryParams, statsPeriod, start, end) {
|
|
|
1122
1276
|
}
|
|
1123
1277
|
function applyStatsMixinTimeParams(queryParams, statsPeriod, start, end, resolutionSeconds) {
|
|
1124
1278
|
if (statsPeriod && (start || end)) {
|
|
1125
|
-
throw new ApiValidationError(
|
|
1279
|
+
throw new ApiValidationError(
|
|
1280
|
+
"Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time."
|
|
1281
|
+
);
|
|
1126
1282
|
}
|
|
1127
1283
|
if (start && !end || !start && end) {
|
|
1128
|
-
throw new ApiValidationError(
|
|
1284
|
+
throw new ApiValidationError(
|
|
1285
|
+
"Both start and end parameters must be provided together for absolute time ranges."
|
|
1286
|
+
);
|
|
1129
1287
|
}
|
|
1130
1288
|
let since;
|
|
1131
1289
|
let until;
|
|
@@ -1133,10 +1291,12 @@ function applyStatsMixinTimeParams(queryParams, statsPeriod, start, end, resolut
|
|
|
1133
1291
|
const startMs = Date.parse(start);
|
|
1134
1292
|
const endMs = Date.parse(end);
|
|
1135
1293
|
if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {
|
|
1136
|
-
throw new ApiValidationError(
|
|
1294
|
+
throw new ApiValidationError(
|
|
1295
|
+
"start and end parameters must be valid ISO 8601 date strings."
|
|
1296
|
+
);
|
|
1137
1297
|
}
|
|
1138
|
-
since = Math.floor(startMs /
|
|
1139
|
-
until = Math.floor(endMs /
|
|
1298
|
+
since = Math.floor(startMs / 1e3);
|
|
1299
|
+
until = Math.floor(endMs / 1e3);
|
|
1140
1300
|
} else {
|
|
1141
1301
|
const period = statsPeriod ?? "24h";
|
|
1142
1302
|
const { amount, unit } = parseStatsPeriod(period);
|
|
@@ -1158,31 +1318,34 @@ function applyStatsMixinTimeParams(queryParams, statsPeriod, start, end, resolut
|
|
|
1158
1318
|
unitSeconds = 60 * 60 * 24 * 7;
|
|
1159
1319
|
break;
|
|
1160
1320
|
default:
|
|
1161
|
-
throw new ApiValidationError(
|
|
1321
|
+
throw new ApiValidationError(
|
|
1322
|
+
"statsPeriod must use a supported relative time format, such as `24h`, `7d`, or `2w`."
|
|
1323
|
+
);
|
|
1162
1324
|
}
|
|
1163
|
-
until = Math.floor(Date.now() /
|
|
1325
|
+
until = Math.floor(Date.now() / 1e3);
|
|
1164
1326
|
since = until - amount * unitSeconds;
|
|
1165
1327
|
}
|
|
1166
1328
|
queryParams.set("since", String(since));
|
|
1167
1329
|
queryParams.set("until", String(until));
|
|
1168
|
-
if (resolutionSeconds !==
|
|
1330
|
+
if (resolutionSeconds !== void 0) {
|
|
1169
1331
|
queryParams.set("resolution", `${resolutionSeconds}s`);
|
|
1170
1332
|
}
|
|
1171
1333
|
}
|
|
1334
|
+
|
|
1172
1335
|
// src/api-client/transport.ts
|
|
1173
|
-
|
|
1336
|
+
var ApiTimeoutError = class extends Error {
|
|
1174
1337
|
timeoutMs;
|
|
1175
1338
|
idempotent;
|
|
1176
1339
|
constructor(timeoutMs, idempotent) {
|
|
1177
|
-
super(`Request timed out after ${Math.round(timeoutMs /
|
|
1340
|
+
super(`Request timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
1178
1341
|
this.name = "ApiTimeoutError";
|
|
1179
1342
|
this.timeoutMs = timeoutMs;
|
|
1180
1343
|
this.idempotent = idempotent;
|
|
1181
1344
|
}
|
|
1182
|
-
}
|
|
1345
|
+
};
|
|
1183
1346
|
var MAX_REDIRECTS = 20;
|
|
1184
1347
|
var ANONYMOUS = { token: "", identity: "" };
|
|
1185
|
-
var validatedSources = new WeakMap;
|
|
1348
|
+
var validatedSources = /* @__PURE__ */ new WeakMap();
|
|
1186
1349
|
function getValidatedResponseSource(response) {
|
|
1187
1350
|
return validatedSources.get(response);
|
|
1188
1351
|
}
|
|
@@ -1203,13 +1366,12 @@ function extractUrlPath(input) {
|
|
|
1203
1366
|
return raw;
|
|
1204
1367
|
}
|
|
1205
1368
|
}
|
|
1206
|
-
|
|
1207
|
-
class Rejection {
|
|
1208
|
-
error;
|
|
1369
|
+
var Rejection = class {
|
|
1209
1370
|
constructor(error) {
|
|
1210
1371
|
this.error = error;
|
|
1211
1372
|
}
|
|
1212
|
-
|
|
1373
|
+
error;
|
|
1374
|
+
};
|
|
1213
1375
|
function createApiTransport(config) {
|
|
1214
1376
|
const { credentials, retry, logger } = config;
|
|
1215
1377
|
const cache = credentials && config.cache;
|
|
@@ -1239,7 +1401,9 @@ function createApiTransport(config) {
|
|
|
1239
1401
|
return typeof config.timeoutMs === "function" ? config.timeoutMs(url) : config.timeoutMs;
|
|
1240
1402
|
}
|
|
1241
1403
|
function buildHeaders(input, init, credential) {
|
|
1242
|
-
const headers = new Headers(
|
|
1404
|
+
const headers = new Headers(
|
|
1405
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
1406
|
+
);
|
|
1243
1407
|
config.prepareHeaders?.(headers, { input, credential });
|
|
1244
1408
|
setAuthorization(headers, authorization(credential));
|
|
1245
1409
|
if (config.userAgent && !headers.has("User-Agent")) {
|
|
@@ -1255,9 +1419,9 @@ function createApiTransport(config) {
|
|
|
1255
1419
|
}
|
|
1256
1420
|
}
|
|
1257
1421
|
async function fetchOnce(input, init, signal, timeoutMs, mode) {
|
|
1258
|
-
const controller = new AbortController;
|
|
1422
|
+
const controller = new AbortController();
|
|
1259
1423
|
let timedOut = false;
|
|
1260
|
-
const timer = timeoutMs ===
|
|
1424
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1261
1425
|
timedOut = true;
|
|
1262
1426
|
controller.abort();
|
|
1263
1427
|
}, timeoutMs);
|
|
@@ -1288,7 +1452,9 @@ function createApiTransport(config) {
|
|
|
1288
1452
|
}
|
|
1289
1453
|
async function rejectRedirect(response, destination, credential) {
|
|
1290
1454
|
cancelBody(response);
|
|
1291
|
-
throw new Rejection(
|
|
1455
|
+
throw new Rejection(
|
|
1456
|
+
config.origins?.reject(destination, credential) ?? new Error(`Refusing to follow a redirect to ${destination}`)
|
|
1457
|
+
);
|
|
1292
1458
|
}
|
|
1293
1459
|
function trusts(url, credential, original) {
|
|
1294
1460
|
return config.origins ? config.origins.trusts(url, credential) : url.origin === original.origin;
|
|
@@ -1305,7 +1471,10 @@ function createApiTransport(config) {
|
|
|
1305
1471
|
try {
|
|
1306
1472
|
next = new URL(location, request2.url);
|
|
1307
1473
|
} catch (error) {
|
|
1308
|
-
logger?.debug(
|
|
1474
|
+
logger?.debug(
|
|
1475
|
+
"Rejected malformed redirect location",
|
|
1476
|
+
error instanceof Error ? error.name : "unknown error"
|
|
1477
|
+
);
|
|
1309
1478
|
return await rejectRedirect(response, "<invalid host>", credential);
|
|
1310
1479
|
}
|
|
1311
1480
|
if (!trusts(next, credential, original)) {
|
|
@@ -1321,11 +1490,17 @@ function createApiTransport(config) {
|
|
|
1321
1490
|
async function fetchValidated(input, init, headers, credential, timeoutMs, mode) {
|
|
1322
1491
|
const original = new Request(input, init);
|
|
1323
1492
|
const originalUrl = new URL(original.url);
|
|
1324
|
-
const visited = new Set([redirectKey(originalUrl)]);
|
|
1493
|
+
const visited = /* @__PURE__ */ new Set([redirectKey(originalUrl)]);
|
|
1325
1494
|
let request2 = original;
|
|
1326
1495
|
let requestHeaders = new Headers(headers);
|
|
1327
|
-
for (
|
|
1328
|
-
const result = await fetchOnce(
|
|
1496
|
+
for (; ; ) {
|
|
1497
|
+
const result = await fetchOnce(
|
|
1498
|
+
request2.clone(),
|
|
1499
|
+
{ headers: requestHeaders, redirect: "manual" },
|
|
1500
|
+
init?.signal ?? original.signal,
|
|
1501
|
+
timeoutMs,
|
|
1502
|
+
mode
|
|
1503
|
+
);
|
|
1329
1504
|
if (!("response" in result) || !isRedirect(result.response.status)) {
|
|
1330
1505
|
const origin = httpOrigin(request2.url);
|
|
1331
1506
|
if ("response" in result && origin) {
|
|
@@ -1338,8 +1513,19 @@ function createApiTransport(config) {
|
|
|
1338
1513
|
}
|
|
1339
1514
|
const { response } = result;
|
|
1340
1515
|
try {
|
|
1341
|
-
const next = await nextRedirect(
|
|
1342
|
-
|
|
1516
|
+
const next = await nextRedirect(
|
|
1517
|
+
response,
|
|
1518
|
+
request2,
|
|
1519
|
+
credential,
|
|
1520
|
+
visited,
|
|
1521
|
+
originalUrl
|
|
1522
|
+
);
|
|
1523
|
+
({ request: request2, headers: requestHeaders } = await redirectRequest(
|
|
1524
|
+
request2,
|
|
1525
|
+
requestHeaders,
|
|
1526
|
+
next,
|
|
1527
|
+
response.status
|
|
1528
|
+
));
|
|
1343
1529
|
} finally {
|
|
1344
1530
|
cancelBody(response);
|
|
1345
1531
|
}
|
|
@@ -1349,14 +1535,16 @@ function createApiTransport(config) {
|
|
|
1349
1535
|
const { input, init, method, url, path, options } = sendArgs;
|
|
1350
1536
|
let credential = credentials?.prepare ? await credentials.prepare(sendArgs.credential) : sendArgs.credential;
|
|
1351
1537
|
const headers = buildHeaders(input, init, credential);
|
|
1352
|
-
for (const [name, value] of Object.entries(
|
|
1538
|
+
for (const [name, value] of Object.entries(
|
|
1539
|
+
sendArgs.conditionalHeaders ?? {}
|
|
1540
|
+
)) {
|
|
1353
1541
|
headers.set(name, value);
|
|
1354
1542
|
}
|
|
1355
1543
|
const nextInput = await replayableInput(input, init);
|
|
1356
1544
|
const timeoutMs = timeoutFor(url);
|
|
1357
1545
|
const idempotent = options.idempotent === true || (config.isIdempotent?.(method, url) ?? false);
|
|
1358
1546
|
const mode = options.responseBody ?? "stream";
|
|
1359
|
-
const signal = init?.signal !==
|
|
1547
|
+
const signal = init?.signal !== void 0 ? init.signal : input instanceof Request ? input.signal : void 0;
|
|
1360
1548
|
let refreshed = false;
|
|
1361
1549
|
let waitedMs = 0;
|
|
1362
1550
|
const retryDelay = (attempt, status, serverDelayMs) => nextRetryDelayMs({
|
|
@@ -1370,7 +1558,7 @@ function createApiTransport(config) {
|
|
|
1370
1558
|
});
|
|
1371
1559
|
async function onResponse(response, attempt) {
|
|
1372
1560
|
if (response.status === 401) {
|
|
1373
|
-
const next = refreshed || !credentials?.refresh ?
|
|
1561
|
+
const next = refreshed || !credentials?.refresh ? void 0 : await credentials.refresh(credential);
|
|
1374
1562
|
if (!next) {
|
|
1375
1563
|
return { action: "done", response };
|
|
1376
1564
|
}
|
|
@@ -1383,9 +1571,12 @@ function createApiTransport(config) {
|
|
|
1383
1571
|
cancelBody(response);
|
|
1384
1572
|
return { action: "replay" };
|
|
1385
1573
|
}
|
|
1386
|
-
const serverDelayMs = parseRetryDelayMs(
|
|
1574
|
+
const serverDelayMs = parseRetryDelayMs(
|
|
1575
|
+
response.status,
|
|
1576
|
+
response.headers
|
|
1577
|
+
);
|
|
1387
1578
|
const delayMs = retryDelay(attempt, response.status, serverDelayMs);
|
|
1388
|
-
if (delayMs !==
|
|
1579
|
+
if (delayMs !== void 0) {
|
|
1389
1580
|
cancelBody(response);
|
|
1390
1581
|
return { action: "retry", delayMs };
|
|
1391
1582
|
}
|
|
@@ -1411,11 +1602,11 @@ function createApiTransport(config) {
|
|
|
1411
1602
|
if (fatal) {
|
|
1412
1603
|
return { action: "throw", error: fatal };
|
|
1413
1604
|
}
|
|
1414
|
-
const delayMs = retryDelay(attempt,
|
|
1415
|
-
if (delayMs !==
|
|
1605
|
+
const delayMs = retryDelay(attempt, void 0, void 0);
|
|
1606
|
+
if (delayMs !== void 0) {
|
|
1416
1607
|
return { action: "retry", delayMs };
|
|
1417
1608
|
}
|
|
1418
|
-
if (timedOutAfterMs ===
|
|
1609
|
+
if (timedOutAfterMs === void 0) {
|
|
1419
1610
|
return { action: "throw", error: failed };
|
|
1420
1611
|
}
|
|
1421
1612
|
const replayable = isIdempotentRequest(method, idempotent);
|
|
@@ -1427,28 +1618,54 @@ function createApiTransport(config) {
|
|
|
1427
1618
|
}) ?? new ApiTimeoutError(timedOutAfterMs, replayable)
|
|
1428
1619
|
};
|
|
1429
1620
|
}
|
|
1430
|
-
for (let attempt = 0
|
|
1621
|
+
for (let attempt = 0; ; ) {
|
|
1431
1622
|
const { input: attemptInput, init: attemptInit } = nextInput();
|
|
1432
1623
|
let outcome;
|
|
1433
1624
|
try {
|
|
1434
|
-
outcome = options.validateRedirects ? await fetchValidated(
|
|
1625
|
+
outcome = options.validateRedirects ? await fetchValidated(
|
|
1626
|
+
attemptInput,
|
|
1627
|
+
attemptInit,
|
|
1628
|
+
headers,
|
|
1629
|
+
credential,
|
|
1630
|
+
timeoutMs,
|
|
1631
|
+
mode
|
|
1632
|
+
) : await fetchOnce(
|
|
1633
|
+
attemptInput,
|
|
1634
|
+
{ ...attemptInit, headers },
|
|
1635
|
+
signal,
|
|
1636
|
+
timeoutMs,
|
|
1637
|
+
mode
|
|
1638
|
+
);
|
|
1435
1639
|
} catch (error) {
|
|
1436
1640
|
outcome = { failed: error };
|
|
1437
1641
|
}
|
|
1438
1642
|
const result = "response" in outcome ? await onResponse(outcome.response, attempt) : onFailure(outcome, attempt);
|
|
1439
1643
|
if (result.action === "done") {
|
|
1440
|
-
storeInCache(
|
|
1441
|
-
|
|
1644
|
+
storeInCache(
|
|
1645
|
+
method,
|
|
1646
|
+
url,
|
|
1647
|
+
sendArgs.credential.identity,
|
|
1648
|
+
headers,
|
|
1649
|
+
result.response
|
|
1650
|
+
);
|
|
1651
|
+
await invalidateAfterWrite(
|
|
1652
|
+
method,
|
|
1653
|
+
url,
|
|
1654
|
+
sendArgs.credential.identity,
|
|
1655
|
+
result.response
|
|
1656
|
+
);
|
|
1442
1657
|
return result.response;
|
|
1443
1658
|
}
|
|
1444
1659
|
if (result.action === "throw") {
|
|
1445
1660
|
throw result.error;
|
|
1446
1661
|
}
|
|
1447
1662
|
if (result.action === "replay") {
|
|
1448
|
-
logger?.debug(`${method} ${path}
|
|
1663
|
+
logger?.debug(`${method} ${path} \u2192 replay with a refreshed credential`);
|
|
1449
1664
|
continue;
|
|
1450
1665
|
}
|
|
1451
|
-
logger?.debug(
|
|
1666
|
+
logger?.debug(
|
|
1667
|
+
`${method} ${path} \u2192 retry ${attempt + 1}/${retry.maxRetries} after ${Math.round(result.delayMs)}ms`
|
|
1668
|
+
);
|
|
1452
1669
|
await sleep(result.delayMs, signal);
|
|
1453
1670
|
waitedMs += result.delayMs;
|
|
1454
1671
|
attempt++;
|
|
@@ -1458,11 +1675,14 @@ function createApiTransport(config) {
|
|
|
1458
1675
|
if (!cache || method !== "GET" || !response.ok) {
|
|
1459
1676
|
return;
|
|
1460
1677
|
}
|
|
1461
|
-
cache.store(
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1678
|
+
cache.store(
|
|
1679
|
+
{
|
|
1680
|
+
url,
|
|
1681
|
+
requestHeaders: { authorization: headers.get("Authorization") ?? "" },
|
|
1682
|
+
identity
|
|
1683
|
+
},
|
|
1684
|
+
response.clone()
|
|
1685
|
+
).catch((error) => {
|
|
1466
1686
|
logger?.debug("Response cache write failed", error);
|
|
1467
1687
|
});
|
|
1468
1688
|
}
|
|
@@ -1488,9 +1708,11 @@ function createApiTransport(config) {
|
|
|
1488
1708
|
url,
|
|
1489
1709
|
requestHeaders: lookupHeaders(credential),
|
|
1490
1710
|
identity: credential.identity
|
|
1491
|
-
}) :
|
|
1711
|
+
}) : void 0;
|
|
1492
1712
|
if (lookup?.kind === "hit") {
|
|
1493
|
-
logger?.debug(
|
|
1713
|
+
logger?.debug(
|
|
1714
|
+
`${method} ${path} \u2192 ${lookup.response.status} (cache hit, ${elapsed()}ms)`
|
|
1715
|
+
);
|
|
1494
1716
|
return lookup.response;
|
|
1495
1717
|
}
|
|
1496
1718
|
const sendArgs = { input, init, method, url, path, credential, options };
|
|
@@ -1501,12 +1723,14 @@ function createApiTransport(config) {
|
|
|
1501
1723
|
if (lookup?.kind === "stale" && response.status === 304) {
|
|
1502
1724
|
const revalidated = await lookup.revalidate(response);
|
|
1503
1725
|
if (revalidated) {
|
|
1504
|
-
logger?.debug(
|
|
1726
|
+
logger?.debug(
|
|
1727
|
+
`${method} ${path} \u2192 304 (revalidated cache entry, ${elapsed()}ms)`
|
|
1728
|
+
);
|
|
1505
1729
|
return revalidated;
|
|
1506
1730
|
}
|
|
1507
1731
|
response = await send(sendArgs);
|
|
1508
1732
|
}
|
|
1509
|
-
logger?.debug(`${method} ${path}
|
|
1733
|
+
logger?.debug(`${method} ${path} \u2192 ${response.status} (${elapsed()}ms)`);
|
|
1510
1734
|
return response;
|
|
1511
1735
|
};
|
|
1512
1736
|
return config.span ? await config.span({ method, path }, run) : await run();
|
|
@@ -1514,7 +1738,7 @@ function createApiTransport(config) {
|
|
|
1514
1738
|
return { request };
|
|
1515
1739
|
}
|
|
1516
1740
|
async function drainBody(response, signal) {
|
|
1517
|
-
const drained = response.body?.pipeTo(new WritableStream);
|
|
1741
|
+
const drained = response.body?.pipeTo(new WritableStream());
|
|
1518
1742
|
if (!drained) {
|
|
1519
1743
|
return;
|
|
1520
1744
|
}
|
|
@@ -1533,7 +1757,8 @@ async function drainBody(response, signal) {
|
|
|
1533
1757
|
if (onAbort) {
|
|
1534
1758
|
signal.removeEventListener("abort", onAbort);
|
|
1535
1759
|
}
|
|
1536
|
-
drained.catch(() => {
|
|
1760
|
+
drained.catch(() => {
|
|
1761
|
+
});
|
|
1537
1762
|
}
|
|
1538
1763
|
}
|
|
1539
1764
|
function isAbort(error, signal) {
|
|
@@ -1558,13 +1783,13 @@ async function replayableInput(input, init) {
|
|
|
1558
1783
|
return () => ({ input, init });
|
|
1559
1784
|
}
|
|
1560
1785
|
function setAuthorization(headers, value) {
|
|
1561
|
-
if (value !==
|
|
1786
|
+
if (value !== void 0) {
|
|
1562
1787
|
headers.set("Authorization", value);
|
|
1563
1788
|
}
|
|
1564
1789
|
}
|
|
1565
1790
|
function httpOrigin(raw) {
|
|
1566
1791
|
const url = new URL(raw);
|
|
1567
|
-
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password ? url.origin :
|
|
1792
|
+
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password ? url.origin : void 0;
|
|
1568
1793
|
}
|
|
1569
1794
|
function isRedirect(status) {
|
|
1570
1795
|
return [301, 302, 303, 307, 308].includes(status);
|
|
@@ -1583,65 +1808,65 @@ async function redirectRequest(request, requestHeaders, next, status) {
|
|
|
1583
1808
|
headers.delete("content-type");
|
|
1584
1809
|
headers.delete("transfer-encoding");
|
|
1585
1810
|
}
|
|
1586
|
-
const body = method === current && request.body ? await request.clone().arrayBuffer() :
|
|
1811
|
+
const body = method === current && request.body ? await request.clone().arrayBuffer() : void 0;
|
|
1587
1812
|
return { request: new Request(next, { method, headers, body }), headers };
|
|
1588
1813
|
}
|
|
1589
1814
|
export {
|
|
1590
|
-
|
|
1591
|
-
validateResponse,
|
|
1592
|
-
trimAuthToken,
|
|
1593
|
-
storeResponse,
|
|
1594
|
-
sleep,
|
|
1595
|
-
sentryBearerHeader,
|
|
1596
|
-
selectIdempotentWrites,
|
|
1597
|
-
readJsonResponse,
|
|
1598
|
-
readErrorResponse,
|
|
1599
|
-
parseStatsPeriod,
|
|
1600
|
-
parseRetryDelayMs,
|
|
1601
|
-
paginate,
|
|
1602
|
-
normalizeUrl,
|
|
1603
|
-
normalizeStatsPeriod,
|
|
1604
|
-
normalizeAuthToken,
|
|
1605
|
-
nextRetryDelayMs,
|
|
1606
|
-
nextPageCursor,
|
|
1607
|
-
lookupResponse,
|
|
1608
|
-
listPage,
|
|
1609
|
-
isIdempotentRequest,
|
|
1610
|
-
isApiAuthenticationErrorDeep,
|
|
1611
|
-
getValidatedResponseSource,
|
|
1612
|
-
fetchAllPages,
|
|
1613
|
-
extractUrlPath,
|
|
1614
|
-
extractInputUrl,
|
|
1615
|
-
encodeApiPathSegment,
|
|
1616
|
-
defaultStatsPeriod,
|
|
1617
|
-
decodePageCursor,
|
|
1618
|
-
createMemoryStore,
|
|
1619
|
-
createIdempotentOperationMatcher,
|
|
1620
|
-
createApiTransport,
|
|
1621
|
-
createApiError,
|
|
1622
|
-
createApiClientCore,
|
|
1623
|
-
consumeCursorPage,
|
|
1624
|
-
computeInvalidationPrefixes,
|
|
1625
|
-
classifyUrl,
|
|
1626
|
-
buildSentryApiUrl,
|
|
1627
|
-
buildCacheKey,
|
|
1628
|
-
applyTimeParams,
|
|
1629
|
-
applyStatsMixinTimeParams,
|
|
1630
|
-
apiPath,
|
|
1631
|
-
UserInputError,
|
|
1632
|
-
REVALIDATION_WINDOW_MS,
|
|
1633
|
-
MAX_LIST_PAGES,
|
|
1634
|
-
FALLBACK_TTL_MS,
|
|
1635
|
-
ConfigurationError,
|
|
1636
|
-
ApiValidationError,
|
|
1637
|
-
ApiTimeoutError,
|
|
1638
|
-
ApiServerError,
|
|
1639
|
-
ApiResponseValidationError,
|
|
1640
|
-
ApiRateLimitError,
|
|
1641
|
-
ApiPermissionError,
|
|
1642
|
-
ApiNotFoundError,
|
|
1643
|
-
ApiError,
|
|
1644
|
-
ApiClientError,
|
|
1815
|
+
API_MAX_PER_PAGE,
|
|
1645
1816
|
ApiAuthenticationError,
|
|
1646
|
-
|
|
1817
|
+
ApiClientError,
|
|
1818
|
+
ApiError,
|
|
1819
|
+
ApiNotFoundError,
|
|
1820
|
+
ApiPermissionError,
|
|
1821
|
+
ApiRateLimitError,
|
|
1822
|
+
ApiResponseValidationError,
|
|
1823
|
+
ApiServerError,
|
|
1824
|
+
ApiTimeoutError,
|
|
1825
|
+
ApiValidationError,
|
|
1826
|
+
ConfigurationError,
|
|
1827
|
+
FALLBACK_TTL_MS,
|
|
1828
|
+
MAX_LIST_PAGES,
|
|
1829
|
+
REVALIDATION_WINDOW_MS,
|
|
1830
|
+
UserInputError,
|
|
1831
|
+
apiPath,
|
|
1832
|
+
applyStatsMixinTimeParams,
|
|
1833
|
+
applyTimeParams,
|
|
1834
|
+
buildCacheKey,
|
|
1835
|
+
buildSentryApiUrl,
|
|
1836
|
+
classifyUrl,
|
|
1837
|
+
computeInvalidationPrefixes,
|
|
1838
|
+
consumeCursorPage,
|
|
1839
|
+
createApiClientCore,
|
|
1840
|
+
createApiError,
|
|
1841
|
+
createApiTransport,
|
|
1842
|
+
createIdempotentOperationMatcher,
|
|
1843
|
+
createMemoryStore,
|
|
1844
|
+
decodePageCursor,
|
|
1845
|
+
defaultStatsPeriod,
|
|
1846
|
+
encodeApiPathSegment,
|
|
1847
|
+
extractInputUrl,
|
|
1848
|
+
extractUrlPath,
|
|
1849
|
+
fetchAllPages,
|
|
1850
|
+
getValidatedResponseSource,
|
|
1851
|
+
isApiAuthenticationErrorDeep,
|
|
1852
|
+
isIdempotentRequest,
|
|
1853
|
+
listPage,
|
|
1854
|
+
lookupResponse,
|
|
1855
|
+
nextPageCursor,
|
|
1856
|
+
nextRetryDelayMs,
|
|
1857
|
+
normalizeAuthToken,
|
|
1858
|
+
normalizeStatsPeriod,
|
|
1859
|
+
normalizeUrl,
|
|
1860
|
+
paginate,
|
|
1861
|
+
parseRetryDelayMs,
|
|
1862
|
+
parseStatsPeriod,
|
|
1863
|
+
readErrorResponse,
|
|
1864
|
+
readJsonResponse,
|
|
1865
|
+
selectIdempotentWrites,
|
|
1866
|
+
sentryBearerHeader,
|
|
1867
|
+
sleep,
|
|
1868
|
+
storeResponse,
|
|
1869
|
+
trimAuthToken,
|
|
1870
|
+
validateResponse,
|
|
1871
|
+
withQuery
|
|
1647
1872
|
};
|