@felixgeelhaar/jira-sdk 0.2.0 → 1.0.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 +214 -374
- package/dist/auth/index.cjs +400 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +192 -0
- package/dist/auth/index.d.ts +192 -0
- package/dist/auth/index.js +386 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/errors/index.cjs +272 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.cts +203 -0
- package/dist/errors/index.d.ts +203 -0
- package/dist/errors/index.js +254 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/http-client-BSzRYQZa.d.cts +317 -0
- package/dist/http-client-erRvYNs-.d.ts +317 -0
- package/dist/index.cjs +9582 -13466
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1242 -76
- package/dist/index.d.ts +1242 -76
- package/dist/index.js +9166 -13318
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.cjs +2481 -13092
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +335 -207
- package/dist/schemas/index.d.ts +335 -207
- package/dist/schemas/index.js +2138 -13047
- package/dist/schemas/index.js.map +1 -1
- package/dist/services/index.cjs +6375 -13323
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.cts +3852 -299
- package/dist/services/index.d.ts +3852 -299
- package/dist/services/index.js +6350 -13321
- package/dist/services/index.js.map +1 -1
- package/dist/transport/index.cjs +1016 -0
- package/dist/transport/index.cjs.map +1 -0
- package/dist/transport/index.d.cts +372 -0
- package/dist/transport/index.d.ts +372 -0
- package/dist/transport/index.js +997 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/types-E6djPHpW.d.cts +95 -0
- package/dist/types-E6djPHpW.d.ts +95 -0
- package/dist/webhook-Bn8gme6Y.d.cts +6959 -0
- package/dist/webhook-Bn8gme6Y.d.ts +6959 -0
- package/package.json +73 -27
- package/dist/project-BtUx-eSv.d.cts +0 -1480
- package/dist/project-BtUx-eSv.d.ts +0 -1480
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
// src/logging/noop-logger.ts
|
|
2
|
+
var NoopLogger = class {
|
|
3
|
+
debug(_message, _fields) {
|
|
4
|
+
}
|
|
5
|
+
info(_message, _fields) {
|
|
6
|
+
}
|
|
7
|
+
warn(_message, _fields) {
|
|
8
|
+
}
|
|
9
|
+
error(_message, _fields) {
|
|
10
|
+
}
|
|
11
|
+
child(_fields) {
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/utils/runtime.ts
|
|
17
|
+
function runtime() {
|
|
18
|
+
return globalThis;
|
|
19
|
+
}
|
|
20
|
+
function processEnv() {
|
|
21
|
+
return runtime().process?.env ?? {};
|
|
22
|
+
}
|
|
23
|
+
function readEnvVar(name) {
|
|
24
|
+
return processEnv()[name];
|
|
25
|
+
}
|
|
26
|
+
function warn(message) {
|
|
27
|
+
runtime().console?.warn?.(message);
|
|
28
|
+
}
|
|
29
|
+
function randomId() {
|
|
30
|
+
const uuid = runtime().crypto?.randomUUID?.();
|
|
31
|
+
if (uuid !== void 0) {
|
|
32
|
+
return uuid;
|
|
33
|
+
}
|
|
34
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
35
|
+
}
|
|
36
|
+
function captureStackTrace(target, constructor) {
|
|
37
|
+
const capture = Error.captureStackTrace;
|
|
38
|
+
capture?.(target, constructor);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/errors/base.error.ts
|
|
42
|
+
var JiraSdkError = class extends Error {
|
|
43
|
+
/** HTTP status code if applicable */
|
|
44
|
+
statusCode;
|
|
45
|
+
/** Original cause of the error */
|
|
46
|
+
cause;
|
|
47
|
+
/** Additional context/metadata */
|
|
48
|
+
context;
|
|
49
|
+
constructor(message, options) {
|
|
50
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
51
|
+
this.name = this.constructor.name;
|
|
52
|
+
if (options?.cause !== void 0) {
|
|
53
|
+
this.cause = options.cause;
|
|
54
|
+
}
|
|
55
|
+
if (options?.statusCode !== void 0) {
|
|
56
|
+
this.statusCode = options.statusCode;
|
|
57
|
+
}
|
|
58
|
+
if (options?.context !== void 0) {
|
|
59
|
+
this.context = options.context;
|
|
60
|
+
}
|
|
61
|
+
captureStackTrace(this, this.constructor);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns a JSON representation of the error
|
|
65
|
+
*/
|
|
66
|
+
toJSON() {
|
|
67
|
+
return {
|
|
68
|
+
name: this.name,
|
|
69
|
+
code: this.code,
|
|
70
|
+
message: this.message,
|
|
71
|
+
statusCode: this.statusCode,
|
|
72
|
+
context: this.context,
|
|
73
|
+
cause: this.cause?.message,
|
|
74
|
+
stack: this.stack
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/errors/network.error.ts
|
|
80
|
+
var NetworkError = class extends JiraSdkError {
|
|
81
|
+
code = "NETWORK_ERROR";
|
|
82
|
+
constructor(message, options) {
|
|
83
|
+
super(message, { cause: options?.cause });
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if this error indicates a connection failure
|
|
87
|
+
*/
|
|
88
|
+
isConnectionError() {
|
|
89
|
+
const msg = this.message.toLowerCase();
|
|
90
|
+
return msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("fetch failed");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if this error might be retryable
|
|
94
|
+
*/
|
|
95
|
+
isRetryable() {
|
|
96
|
+
const msg = this.message.toLowerCase();
|
|
97
|
+
return msg.includes("econnreset") || msg.includes("etimedout") || msg.includes("temporary");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var TimeoutError = class extends JiraSdkError {
|
|
101
|
+
code = "TIMEOUT_ERROR";
|
|
102
|
+
/** The timeout duration in milliseconds */
|
|
103
|
+
timeoutMs;
|
|
104
|
+
constructor(message, options) {
|
|
105
|
+
super(message, { cause: options?.cause });
|
|
106
|
+
this.timeoutMs = options?.timeoutMs;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var AbortError = class extends JiraSdkError {
|
|
110
|
+
code = "ABORT_ERROR";
|
|
111
|
+
constructor(message = "Request was aborted", options) {
|
|
112
|
+
super(message, { cause: options?.cause });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/errors/api.error.ts
|
|
117
|
+
var ApiError = class extends JiraSdkError {
|
|
118
|
+
code = "JIRA_API_ERROR";
|
|
119
|
+
/** Parsed error details from Jira response */
|
|
120
|
+
details;
|
|
121
|
+
/** Raw response body */
|
|
122
|
+
responseBody;
|
|
123
|
+
constructor(message, statusCode, responseBody) {
|
|
124
|
+
const details = parseErrorBody(responseBody);
|
|
125
|
+
const finalMessage = details ? buildErrorMessage(message, details) : message;
|
|
126
|
+
super(finalMessage, { statusCode, context: { responseBody } });
|
|
127
|
+
this.details = details;
|
|
128
|
+
this.responseBody = responseBody;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Returns all error messages as a single array
|
|
132
|
+
*/
|
|
133
|
+
getAllMessages() {
|
|
134
|
+
const messages = [];
|
|
135
|
+
if (this.details?.errorMessages) {
|
|
136
|
+
messages.push(...this.details.errorMessages);
|
|
137
|
+
}
|
|
138
|
+
if (this.details?.errors) {
|
|
139
|
+
for (const [field, msg] of Object.entries(this.details.errors)) {
|
|
140
|
+
messages.push(`${field}: ${msg}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (messages.length === 0) {
|
|
144
|
+
messages.push(this.message);
|
|
145
|
+
}
|
|
146
|
+
return messages;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var UnauthorizedError = class extends ApiError {
|
|
150
|
+
code = "JIRA_UNAUTHORIZED";
|
|
151
|
+
constructor(message = "Authentication required", responseBody) {
|
|
152
|
+
super(message, 401, responseBody);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var ForbiddenError = class extends ApiError {
|
|
156
|
+
code = "JIRA_FORBIDDEN";
|
|
157
|
+
constructor(message = "Permission denied", responseBody) {
|
|
158
|
+
super(message, 403, responseBody);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var NotFoundError = class extends ApiError {
|
|
162
|
+
code = "JIRA_NOT_FOUND";
|
|
163
|
+
constructor(message = "Resource not found", responseBody) {
|
|
164
|
+
super(message, 404, responseBody);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var RateLimitError = class extends ApiError {
|
|
168
|
+
code = "JIRA_RATE_LIMITED";
|
|
169
|
+
/** When to retry (in milliseconds) */
|
|
170
|
+
retryAfter;
|
|
171
|
+
constructor(message = "Rate limit exceeded", responseBody, options) {
|
|
172
|
+
super(message, 429, responseBody);
|
|
173
|
+
this.retryAfter = options?.retryAfter;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var ServerError = class extends ApiError {
|
|
177
|
+
code = "JIRA_SERVER_ERROR";
|
|
178
|
+
constructor(message = "Internal server error", responseBody, statusCode = 500) {
|
|
179
|
+
super(message, statusCode, responseBody);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function parseErrorBody(body) {
|
|
183
|
+
if (!body || typeof body !== "object") {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
const obj = body;
|
|
187
|
+
return {
|
|
188
|
+
errorMessages: Array.isArray(obj["errorMessages"]) ? obj["errorMessages"] : void 0,
|
|
189
|
+
errors: typeof obj["errors"] === "object" && obj["errors"] !== null ? obj["errors"] : void 0,
|
|
190
|
+
warningMessages: Array.isArray(obj["warningMessages"]) ? obj["warningMessages"] : void 0
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function buildErrorMessage(baseMessage, details) {
|
|
194
|
+
if (details.errorMessages?.length) {
|
|
195
|
+
return details.errorMessages.join("; ");
|
|
196
|
+
}
|
|
197
|
+
if (details.errors && Object.keys(details.errors).length > 0) {
|
|
198
|
+
const fieldErrors = Object.entries(details.errors).map(([field, msg]) => `${field}: ${msg}`).join("; ");
|
|
199
|
+
return `${baseMessage}: ${fieldErrors}`;
|
|
200
|
+
}
|
|
201
|
+
return baseMessage;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/utils/index.ts
|
|
205
|
+
function sleep(ms) {
|
|
206
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
207
|
+
}
|
|
208
|
+
function buildQueryString(params) {
|
|
209
|
+
const filtered = Object.entries(params).filter(
|
|
210
|
+
([, value]) => value !== void 0 && value !== null && value !== ""
|
|
211
|
+
);
|
|
212
|
+
if (filtered.length === 0) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
const searchParams = new URLSearchParams();
|
|
216
|
+
for (const [key, value] of filtered) {
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
for (const item of value) {
|
|
219
|
+
searchParams.append(key, String(item));
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
searchParams.set(key, String(value));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return searchParams.toString();
|
|
226
|
+
}
|
|
227
|
+
function joinPath(...segments) {
|
|
228
|
+
return segments.map(String).map((s) => s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
229
|
+
}
|
|
230
|
+
function exponentialBackoff(attempt, baseDelay, maxDelay, multiplier = 2, jitter = true) {
|
|
231
|
+
const delay = Math.min(baseDelay * Math.pow(multiplier, attempt), maxDelay);
|
|
232
|
+
if (jitter) {
|
|
233
|
+
const jitterAmount = delay * 0.25;
|
|
234
|
+
return delay + (Math.random() * 2 - 1) * jitterAmount;
|
|
235
|
+
}
|
|
236
|
+
return delay;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/transport/http-client.ts
|
|
240
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
241
|
+
function validateSecureUrl(url, allowInsecure) {
|
|
242
|
+
const isHttps = url.startsWith("https://");
|
|
243
|
+
const isLocalhost = url.includes("localhost") || url.includes("127.0.0.1");
|
|
244
|
+
const isProduction = readEnvVar("NODE_ENV") === "production";
|
|
245
|
+
if (!isHttps && !isLocalhost && !allowInsecure) {
|
|
246
|
+
if (isProduction) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Security Error: HTTPS is required for production environments. URL "${url}" uses HTTP. Use HTTPS or set allowInsecureHttp: true for testing.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
warn(
|
|
252
|
+
`[SDK Warning] Using insecure HTTP connection to "${url}". HTTPS is strongly recommended for production use.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
var HttpClient = class {
|
|
257
|
+
baseUrl;
|
|
258
|
+
auth;
|
|
259
|
+
logger;
|
|
260
|
+
timeout;
|
|
261
|
+
defaultHeaders;
|
|
262
|
+
middleware;
|
|
263
|
+
fetchFn;
|
|
264
|
+
middlewareChain;
|
|
265
|
+
constructor(config) {
|
|
266
|
+
validateSecureUrl(config.baseUrl, config.allowInsecureHttp);
|
|
267
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
268
|
+
this.auth = config.auth;
|
|
269
|
+
this.logger = config.logger ?? new NoopLogger();
|
|
270
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
271
|
+
this.defaultHeaders = {
|
|
272
|
+
"Content-Type": "application/json",
|
|
273
|
+
Accept: "application/json",
|
|
274
|
+
...config.defaultHeaders
|
|
275
|
+
};
|
|
276
|
+
this.middleware = config.middleware ?? [];
|
|
277
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
278
|
+
this.middlewareChain = this.buildMiddlewareChain();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Make a GET request
|
|
282
|
+
*/
|
|
283
|
+
async get(path, params, options) {
|
|
284
|
+
return this.request({
|
|
285
|
+
...options,
|
|
286
|
+
method: "GET",
|
|
287
|
+
url: path,
|
|
288
|
+
// Merge rather than let one clobber the other: callers may pass params
|
|
289
|
+
// positionally and additionally supply some via options.
|
|
290
|
+
params: { ...params, ...options?.params }
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Make a POST request
|
|
295
|
+
*/
|
|
296
|
+
async post(path, body, options) {
|
|
297
|
+
return this.request({
|
|
298
|
+
method: "POST",
|
|
299
|
+
url: path,
|
|
300
|
+
body,
|
|
301
|
+
...options
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Make a PUT request
|
|
306
|
+
*/
|
|
307
|
+
async put(path, body, options) {
|
|
308
|
+
return this.request({
|
|
309
|
+
method: "PUT",
|
|
310
|
+
url: path,
|
|
311
|
+
body,
|
|
312
|
+
...options
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Make a PATCH request
|
|
317
|
+
*/
|
|
318
|
+
async patch(path, body, options) {
|
|
319
|
+
return this.request({
|
|
320
|
+
method: "PATCH",
|
|
321
|
+
url: path,
|
|
322
|
+
body,
|
|
323
|
+
...options
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Make a DELETE request
|
|
328
|
+
*/
|
|
329
|
+
async delete(path, options) {
|
|
330
|
+
return this.request({
|
|
331
|
+
method: "DELETE",
|
|
332
|
+
url: path,
|
|
333
|
+
...options
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Make a request with full control over the configuration
|
|
338
|
+
*/
|
|
339
|
+
async request(requestConfig) {
|
|
340
|
+
const {
|
|
341
|
+
method = "GET",
|
|
342
|
+
url,
|
|
343
|
+
headers,
|
|
344
|
+
body,
|
|
345
|
+
params,
|
|
346
|
+
timeout,
|
|
347
|
+
signal,
|
|
348
|
+
metadata,
|
|
349
|
+
skipAuth
|
|
350
|
+
} = requestConfig;
|
|
351
|
+
const httpRequest = {
|
|
352
|
+
method,
|
|
353
|
+
url,
|
|
354
|
+
headers: { ...this.defaultHeaders, ...headers },
|
|
355
|
+
body,
|
|
356
|
+
params,
|
|
357
|
+
timeout: timeout ?? this.timeout,
|
|
358
|
+
signal,
|
|
359
|
+
metadata
|
|
360
|
+
};
|
|
361
|
+
const context = {
|
|
362
|
+
request: httpRequest,
|
|
363
|
+
auth: skipAuth ? void 0 : this.auth,
|
|
364
|
+
logger: this.logger,
|
|
365
|
+
retryCount: 0
|
|
366
|
+
};
|
|
367
|
+
const chain = this.middlewareChain ?? this.buildMiddlewareChain();
|
|
368
|
+
return chain(context);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Build the middleware chain with the final request handler
|
|
372
|
+
*/
|
|
373
|
+
buildMiddlewareChain() {
|
|
374
|
+
const finalHandler = async (context) => {
|
|
375
|
+
return this.executeRequest(context);
|
|
376
|
+
};
|
|
377
|
+
return this.middleware.reduceRight((next, middleware) => {
|
|
378
|
+
return async (ctx) => middleware(ctx, next);
|
|
379
|
+
}, finalHandler);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Execute the actual HTTP request
|
|
383
|
+
*/
|
|
384
|
+
async executeRequest(context) {
|
|
385
|
+
const { request, auth } = context;
|
|
386
|
+
const startTime = Date.now();
|
|
387
|
+
let fullUrl = request.url.startsWith("http") ? request.url : joinPath(this.baseUrl, request.url);
|
|
388
|
+
if (request.params) {
|
|
389
|
+
const queryString = buildQueryString(request.params);
|
|
390
|
+
if (queryString) {
|
|
391
|
+
fullUrl += (fullUrl.includes("?") ? "&" : "?") + queryString;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const headers = new Headers(request.headers);
|
|
395
|
+
if (auth) {
|
|
396
|
+
const authHeaders = await auth.getAuthHeaders();
|
|
397
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
398
|
+
headers.set(key, value);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
let bodyContent;
|
|
402
|
+
if (request.body !== void 0) {
|
|
403
|
+
if (request.body instanceof FormData || request.body instanceof Blob) {
|
|
404
|
+
bodyContent = request.body;
|
|
405
|
+
headers.delete("Content-Type");
|
|
406
|
+
} else if (typeof request.body === "string") {
|
|
407
|
+
bodyContent = request.body;
|
|
408
|
+
} else {
|
|
409
|
+
bodyContent = JSON.stringify(request.body);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const controller = new AbortController();
|
|
413
|
+
const timeoutId = setTimeout(() => {
|
|
414
|
+
controller.abort();
|
|
415
|
+
}, request.timeout ?? this.timeout);
|
|
416
|
+
const combinedSignal = request.signal ? this.combineSignals(request.signal, controller.signal) : controller.signal;
|
|
417
|
+
try {
|
|
418
|
+
const response = await this.fetchFn(fullUrl, {
|
|
419
|
+
method: request.method,
|
|
420
|
+
headers,
|
|
421
|
+
...bodyContent !== void 0 && { body: bodyContent },
|
|
422
|
+
signal: combinedSignal
|
|
423
|
+
});
|
|
424
|
+
clearTimeout(timeoutId);
|
|
425
|
+
const responseTime = Date.now() - startTime;
|
|
426
|
+
const contentType = response.headers.get("content-type");
|
|
427
|
+
let data;
|
|
428
|
+
const wantBlob = request.metadata?.["rawResponse"] === true;
|
|
429
|
+
if (wantBlob) {
|
|
430
|
+
data = await response.blob();
|
|
431
|
+
} else if (contentType?.includes("application/json")) {
|
|
432
|
+
const text = await response.text();
|
|
433
|
+
data = text ? JSON.parse(text) : null;
|
|
434
|
+
} else {
|
|
435
|
+
data = await response.text();
|
|
436
|
+
}
|
|
437
|
+
if (!response.ok) {
|
|
438
|
+
this.handleErrorResponse(response, data);
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
status: response.status,
|
|
442
|
+
statusText: response.statusText,
|
|
443
|
+
headers: response.headers,
|
|
444
|
+
data,
|
|
445
|
+
request,
|
|
446
|
+
responseTime
|
|
447
|
+
};
|
|
448
|
+
} catch (error) {
|
|
449
|
+
clearTimeout(timeoutId);
|
|
450
|
+
if (error instanceof ApiError) {
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
if (error instanceof Error) {
|
|
454
|
+
if (error.name === "AbortError") {
|
|
455
|
+
if (request.signal?.aborted) {
|
|
456
|
+
throw new AbortError("Request was aborted", { cause: error });
|
|
457
|
+
}
|
|
458
|
+
throw new TimeoutError(`Request timed out after ${request.timeout ?? this.timeout}ms`, {
|
|
459
|
+
cause: error
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
throw new NetworkError(`Network error: ${error.message}`, {
|
|
463
|
+
cause: error
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
throw new NetworkError("Unknown network error", { cause: error });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Handle error responses and throw appropriate errors
|
|
471
|
+
*/
|
|
472
|
+
handleErrorResponse(response, data) {
|
|
473
|
+
const status = response.status;
|
|
474
|
+
switch (status) {
|
|
475
|
+
case 401:
|
|
476
|
+
throw new UnauthorizedError("Authentication required", data);
|
|
477
|
+
case 403:
|
|
478
|
+
throw new ForbiddenError("Access denied", data);
|
|
479
|
+
case 404:
|
|
480
|
+
throw new NotFoundError("Resource not found", data);
|
|
481
|
+
case 429: {
|
|
482
|
+
const retryAfter = this.parseRetryAfter(response.headers);
|
|
483
|
+
throw new RateLimitError(
|
|
484
|
+
"Rate limit exceeded",
|
|
485
|
+
data,
|
|
486
|
+
retryAfter !== void 0 ? { retryAfter } : void 0
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
default:
|
|
490
|
+
if (status >= 500) {
|
|
491
|
+
throw new ServerError(`Server error: ${response.statusText}`, data, status);
|
|
492
|
+
}
|
|
493
|
+
throw new ApiError(`API error: ${response.statusText}`, status, data);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Parse Retry-After header
|
|
498
|
+
*/
|
|
499
|
+
parseRetryAfter(headers) {
|
|
500
|
+
const retryAfter = headers.get("retry-after");
|
|
501
|
+
if (!retryAfter) return void 0;
|
|
502
|
+
const seconds = parseInt(retryAfter, 10);
|
|
503
|
+
if (!isNaN(seconds)) {
|
|
504
|
+
return seconds * 1e3;
|
|
505
|
+
}
|
|
506
|
+
const date = Date.parse(retryAfter);
|
|
507
|
+
if (!isNaN(date)) {
|
|
508
|
+
return Math.max(0, date - Date.now());
|
|
509
|
+
}
|
|
510
|
+
return void 0;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Combine multiple AbortSignals
|
|
514
|
+
*/
|
|
515
|
+
combineSignals(signal1, signal2) {
|
|
516
|
+
const controller = new AbortController();
|
|
517
|
+
const abort = () => {
|
|
518
|
+
controller.abort();
|
|
519
|
+
};
|
|
520
|
+
if (signal1.aborted || signal2.aborted) {
|
|
521
|
+
controller.abort();
|
|
522
|
+
return controller.signal;
|
|
523
|
+
}
|
|
524
|
+
signal1.addEventListener("abort", abort);
|
|
525
|
+
signal2.addEventListener("abort", abort);
|
|
526
|
+
return controller.signal;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Add middleware to the chain
|
|
530
|
+
* Note: This rebuilds the middleware chain for the new configuration
|
|
531
|
+
*/
|
|
532
|
+
use(middleware) {
|
|
533
|
+
this.middleware.push(middleware);
|
|
534
|
+
this.middlewareChain = this.buildMiddlewareChain();
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Get the base URL
|
|
539
|
+
*/
|
|
540
|
+
getBaseUrl() {
|
|
541
|
+
return this.baseUrl;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get the auth provider
|
|
545
|
+
*/
|
|
546
|
+
getAuth() {
|
|
547
|
+
return this.auth;
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
function createHttpClient(config) {
|
|
551
|
+
return new HttpClient(config);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/transport/middleware.ts
|
|
555
|
+
function createLoggingMiddleware(config = {}) {
|
|
556
|
+
const {
|
|
557
|
+
logRequests = true,
|
|
558
|
+
logResponses = true,
|
|
559
|
+
logErrors = true,
|
|
560
|
+
redactHeaders = ["authorization", "x-api-key"]
|
|
561
|
+
} = config;
|
|
562
|
+
const redactHeadersLower = redactHeaders.map((h) => h.toLowerCase());
|
|
563
|
+
return async (context, next) => {
|
|
564
|
+
const { request, logger } = context;
|
|
565
|
+
if (logRequests) {
|
|
566
|
+
const headers = { ...request.headers };
|
|
567
|
+
for (const key of Object.keys(headers)) {
|
|
568
|
+
if (redactHeadersLower.includes(key.toLowerCase())) {
|
|
569
|
+
headers[key] = "[REDACTED]";
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
logger.debug("HTTP Request", {
|
|
573
|
+
method: request.method,
|
|
574
|
+
url: request.url,
|
|
575
|
+
headers,
|
|
576
|
+
params: request.params
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const response = await next(context);
|
|
581
|
+
if (logResponses) {
|
|
582
|
+
logger.debug("HTTP Response", {
|
|
583
|
+
status: response.status,
|
|
584
|
+
statusText: response.statusText,
|
|
585
|
+
responseTime: response.responseTime
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return response;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (logErrors) {
|
|
591
|
+
logger.error("HTTP Error", {
|
|
592
|
+
method: request.method,
|
|
593
|
+
url: request.url,
|
|
594
|
+
error: error instanceof Error ? error.message : String(error)
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function defaultShouldRetry(error) {
|
|
602
|
+
if (error instanceof RateLimitError) {
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
if (error instanceof ServerError) {
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
if (error instanceof NetworkError) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
if (error instanceof ApiError) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
function createRetryMiddleware(config = {}) {
|
|
617
|
+
const {
|
|
618
|
+
maxRetries = 3,
|
|
619
|
+
initialDelayMs = 1e3,
|
|
620
|
+
maxDelayMs = 3e4,
|
|
621
|
+
multiplier = 2,
|
|
622
|
+
jitter = true,
|
|
623
|
+
shouldRetry = defaultShouldRetry
|
|
624
|
+
} = config;
|
|
625
|
+
return async (context, next) => {
|
|
626
|
+
let lastError;
|
|
627
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
628
|
+
try {
|
|
629
|
+
context.retryCount = attempt;
|
|
630
|
+
return await next(context);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
lastError = error;
|
|
633
|
+
if (attempt >= maxRetries || !shouldRetry(error, context)) {
|
|
634
|
+
throw error;
|
|
635
|
+
}
|
|
636
|
+
let delayMs;
|
|
637
|
+
if (error instanceof RateLimitError && error.retryAfter) {
|
|
638
|
+
delayMs = error.retryAfter;
|
|
639
|
+
} else {
|
|
640
|
+
delayMs = exponentialBackoff(attempt, initialDelayMs, maxDelayMs, multiplier, jitter);
|
|
641
|
+
}
|
|
642
|
+
context.logger.debug("Retrying request", {
|
|
643
|
+
attempt: attempt + 1,
|
|
644
|
+
maxRetries,
|
|
645
|
+
delayMs,
|
|
646
|
+
error: error instanceof Error ? error.message : String(error)
|
|
647
|
+
});
|
|
648
|
+
await sleep(delayMs);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
throw lastError;
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function createRateLimitMiddleware(config) {
|
|
655
|
+
const { maxRequests, windowMs, waitForSlot = true } = config;
|
|
656
|
+
const timestamps = [];
|
|
657
|
+
return async (context, next) => {
|
|
658
|
+
const now = Date.now();
|
|
659
|
+
const cutoff = now - windowMs;
|
|
660
|
+
for (let oldest = timestamps[0]; oldest !== void 0 && oldest < cutoff; oldest = timestamps[0]) {
|
|
661
|
+
timestamps.shift();
|
|
662
|
+
}
|
|
663
|
+
if (timestamps.length >= maxRequests) {
|
|
664
|
+
if (waitForSlot) {
|
|
665
|
+
const oldestTimestamp = timestamps[0] ?? now;
|
|
666
|
+
const waitTime = oldestTimestamp + windowMs - now;
|
|
667
|
+
if (waitTime > 0) {
|
|
668
|
+
context.logger.debug("Rate limit reached, waiting", { waitTime });
|
|
669
|
+
await sleep(waitTime);
|
|
670
|
+
}
|
|
671
|
+
timestamps.shift();
|
|
672
|
+
} else {
|
|
673
|
+
throw new RateLimitError("Client-side rate limit exceeded", void 0, {
|
|
674
|
+
retryAfter: (timestamps[0] ?? now) + windowMs - now
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
timestamps.push(now);
|
|
679
|
+
return next(context);
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function createRequestIdMiddleware(headerName = "X-Request-ID") {
|
|
683
|
+
return async (context, next) => {
|
|
684
|
+
const requestId = generateRequestId();
|
|
685
|
+
context.request.headers = {
|
|
686
|
+
...context.request.headers,
|
|
687
|
+
[headerName]: requestId
|
|
688
|
+
};
|
|
689
|
+
context.requestId = requestId;
|
|
690
|
+
return next(context);
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function generateRequestId() {
|
|
694
|
+
return randomId();
|
|
695
|
+
}
|
|
696
|
+
function createUserAgentMiddleware(config) {
|
|
697
|
+
const { sdkName, sdkVersion, suffix } = config;
|
|
698
|
+
let userAgent = `${sdkName}/${sdkVersion}`;
|
|
699
|
+
if (typeof process !== "undefined" && process.version) {
|
|
700
|
+
userAgent += ` Node.js/${process.version.slice(1)}`;
|
|
701
|
+
} else if (typeof navigator !== "undefined") {
|
|
702
|
+
userAgent += " Browser";
|
|
703
|
+
}
|
|
704
|
+
if (suffix) {
|
|
705
|
+
userAgent += ` ${suffix}`;
|
|
706
|
+
}
|
|
707
|
+
return async (context, next) => {
|
|
708
|
+
context.request.headers = {
|
|
709
|
+
...context.request.headers,
|
|
710
|
+
"User-Agent": userAgent
|
|
711
|
+
};
|
|
712
|
+
return next(context);
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function composeMiddleware(...middlewares) {
|
|
716
|
+
return async (context, next) => {
|
|
717
|
+
const chain = middlewares.reduceRight((nextFn, middleware) => {
|
|
718
|
+
return async (ctx) => middleware(ctx, nextFn);
|
|
719
|
+
}, next);
|
|
720
|
+
return chain(context);
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/transport/circuit-breaker.ts
|
|
725
|
+
var CircuitState = /* @__PURE__ */ ((CircuitState2) => {
|
|
726
|
+
CircuitState2["CLOSED"] = "CLOSED";
|
|
727
|
+
CircuitState2["OPEN"] = "OPEN";
|
|
728
|
+
CircuitState2["HALF_OPEN"] = "HALF_OPEN";
|
|
729
|
+
return CircuitState2;
|
|
730
|
+
})(CircuitState || {});
|
|
731
|
+
var CircuitBreakerOpenError = class extends Error {
|
|
732
|
+
code = "CIRCUIT_BREAKER_OPEN";
|
|
733
|
+
remainingMs;
|
|
734
|
+
constructor(message, remainingMs) {
|
|
735
|
+
super(message);
|
|
736
|
+
this.name = "CircuitBreakerOpenError";
|
|
737
|
+
this.remainingMs = remainingMs;
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
var CircuitBreaker = class {
|
|
741
|
+
state = "CLOSED" /* CLOSED */;
|
|
742
|
+
failures = [];
|
|
743
|
+
lastFailureTime = 0;
|
|
744
|
+
successCount = 0;
|
|
745
|
+
failureThreshold;
|
|
746
|
+
resetTimeoutMs;
|
|
747
|
+
failureWindowMs;
|
|
748
|
+
successThreshold;
|
|
749
|
+
isFailure;
|
|
750
|
+
onStateChange;
|
|
751
|
+
constructor(config = {}) {
|
|
752
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
753
|
+
this.resetTimeoutMs = config.resetTimeoutMs ?? 3e4;
|
|
754
|
+
this.failureWindowMs = config.failureWindowMs ?? 6e4;
|
|
755
|
+
this.successThreshold = config.successThreshold ?? 1;
|
|
756
|
+
this.isFailure = config.isFailure ?? defaultIsFailure;
|
|
757
|
+
this.onStateChange = config.onStateChange;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Get current circuit state
|
|
761
|
+
*/
|
|
762
|
+
getState() {
|
|
763
|
+
this.updateState();
|
|
764
|
+
return this.state;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Check if request is allowed
|
|
768
|
+
*/
|
|
769
|
+
canExecute() {
|
|
770
|
+
this.updateState();
|
|
771
|
+
return this.state !== "OPEN" /* OPEN */;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Record a successful request
|
|
775
|
+
*/
|
|
776
|
+
recordSuccess() {
|
|
777
|
+
if (this.state === "HALF_OPEN" /* HALF_OPEN */) {
|
|
778
|
+
this.successCount++;
|
|
779
|
+
if (this.successCount >= this.successThreshold) {
|
|
780
|
+
this.transitionTo("CLOSED" /* CLOSED */);
|
|
781
|
+
this.reset();
|
|
782
|
+
}
|
|
783
|
+
} else if (this.state === "CLOSED" /* CLOSED */) {
|
|
784
|
+
this.cleanupFailures();
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Record a failed request
|
|
789
|
+
*/
|
|
790
|
+
recordFailure(error) {
|
|
791
|
+
if (!this.isFailure(error)) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const now = Date.now();
|
|
795
|
+
this.failures.push(now);
|
|
796
|
+
this.lastFailureTime = now;
|
|
797
|
+
this.cleanupFailures();
|
|
798
|
+
if (this.state === "HALF_OPEN" /* HALF_OPEN */) {
|
|
799
|
+
this.transitionTo("OPEN" /* OPEN */);
|
|
800
|
+
this.successCount = 0;
|
|
801
|
+
} else if (this.state === "CLOSED" /* CLOSED */) {
|
|
802
|
+
if (this.failures.length >= this.failureThreshold) {
|
|
803
|
+
this.transitionTo("OPEN" /* OPEN */);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Get remaining time until circuit can transition to half-open
|
|
809
|
+
*/
|
|
810
|
+
getRemainingOpenTime() {
|
|
811
|
+
if (this.state !== "OPEN" /* OPEN */) {
|
|
812
|
+
return 0;
|
|
813
|
+
}
|
|
814
|
+
const elapsed = Date.now() - this.lastFailureTime;
|
|
815
|
+
return Math.max(0, this.resetTimeoutMs - elapsed);
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Force reset the circuit breaker
|
|
819
|
+
*/
|
|
820
|
+
reset() {
|
|
821
|
+
this.failures = [];
|
|
822
|
+
this.successCount = 0;
|
|
823
|
+
this.lastFailureTime = 0;
|
|
824
|
+
if (this.state !== "CLOSED" /* CLOSED */) {
|
|
825
|
+
this.transitionTo("CLOSED" /* CLOSED */);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Get circuit breaker statistics
|
|
830
|
+
*/
|
|
831
|
+
getStats() {
|
|
832
|
+
this.cleanupFailures();
|
|
833
|
+
return {
|
|
834
|
+
state: this.getState(),
|
|
835
|
+
failureCount: this.failures.length,
|
|
836
|
+
successCount: this.successCount,
|
|
837
|
+
remainingOpenTimeMs: this.getRemainingOpenTime()
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
updateState() {
|
|
841
|
+
if (this.state === "OPEN" /* OPEN */) {
|
|
842
|
+
const elapsed = Date.now() - this.lastFailureTime;
|
|
843
|
+
if (elapsed >= this.resetTimeoutMs) {
|
|
844
|
+
this.transitionTo("HALF_OPEN" /* HALF_OPEN */);
|
|
845
|
+
this.successCount = 0;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
transitionTo(newState) {
|
|
850
|
+
if (this.state !== newState) {
|
|
851
|
+
const oldState = this.state;
|
|
852
|
+
this.state = newState;
|
|
853
|
+
this.onStateChange?.(oldState, newState);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
cleanupFailures() {
|
|
857
|
+
const cutoff = Date.now() - this.failureWindowMs;
|
|
858
|
+
this.failures = this.failures.filter((t) => t > cutoff);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
function defaultIsFailure(error) {
|
|
862
|
+
if (error instanceof NetworkError) {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
if (error instanceof ServerError) {
|
|
866
|
+
return true;
|
|
867
|
+
}
|
|
868
|
+
if (error instanceof RateLimitError) {
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
if (error instanceof ApiError && error.statusCode !== void 0) {
|
|
872
|
+
return error.statusCode >= 500;
|
|
873
|
+
}
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
function createCircuitBreakerMiddleware(circuitBreaker) {
|
|
877
|
+
return async (context, next) => {
|
|
878
|
+
if (!circuitBreaker.canExecute()) {
|
|
879
|
+
const remainingMs = circuitBreaker.getRemainingOpenTime();
|
|
880
|
+
context.logger.warn("Circuit breaker open, rejecting request", {
|
|
881
|
+
remainingMs,
|
|
882
|
+
url: context.request.url
|
|
883
|
+
});
|
|
884
|
+
throw new CircuitBreakerOpenError(
|
|
885
|
+
`Circuit breaker is open. Retry after ${remainingMs}ms`,
|
|
886
|
+
remainingMs
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
const response = await next(context);
|
|
891
|
+
circuitBreaker.recordSuccess();
|
|
892
|
+
return response;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
circuitBreaker.recordFailure(error);
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function createDefaultCircuitBreaker(config) {
|
|
900
|
+
return new CircuitBreaker(config);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/transport/rate-limit-headers.ts
|
|
904
|
+
function parseBetaRateLimitPolicy(header) {
|
|
905
|
+
if (header === null || header === void 0 || header === "") {
|
|
906
|
+
return void 0;
|
|
907
|
+
}
|
|
908
|
+
const parts = header.split(";");
|
|
909
|
+
const rawLimit = parts[0]?.trim();
|
|
910
|
+
if (rawLimit === void 0 || rawLimit === "") {
|
|
911
|
+
return void 0;
|
|
912
|
+
}
|
|
913
|
+
const limit = Number.parseInt(rawLimit, 10);
|
|
914
|
+
if (!Number.isFinite(limit)) {
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
917
|
+
let windowSeconds = 0;
|
|
918
|
+
for (const part of parts.slice(1)) {
|
|
919
|
+
const [key, value] = part.trim().split("=", 2);
|
|
920
|
+
if (key === "w" && value !== void 0) {
|
|
921
|
+
const parsed = Number.parseInt(value, 10);
|
|
922
|
+
if (Number.isFinite(parsed)) {
|
|
923
|
+
windowSeconds = parsed;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return { limit, windowSeconds };
|
|
928
|
+
}
|
|
929
|
+
function parseBetaRateLimit(header) {
|
|
930
|
+
if (header === null || header === void 0 || header === "") {
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
for (const part of header.split(";")) {
|
|
934
|
+
const [key, value] = part.trim().split("=", 2);
|
|
935
|
+
if (key === "r" && value !== void 0) {
|
|
936
|
+
const remaining = Number.parseInt(value, 10);
|
|
937
|
+
if (Number.isFinite(remaining)) {
|
|
938
|
+
return remaining;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return void 0;
|
|
943
|
+
}
|
|
944
|
+
function parseRetryAfterSeconds(header, now = Date.now()) {
|
|
945
|
+
if (header === null || header === void 0 || header === "") {
|
|
946
|
+
return void 0;
|
|
947
|
+
}
|
|
948
|
+
const seconds = Number.parseInt(header.trim(), 10);
|
|
949
|
+
if (Number.isFinite(seconds) && String(seconds) === header.trim()) {
|
|
950
|
+
return Math.max(0, seconds);
|
|
951
|
+
}
|
|
952
|
+
const date = Date.parse(header);
|
|
953
|
+
if (Number.isFinite(date)) {
|
|
954
|
+
return Math.max(0, Math.ceil((date - now) / 1e3));
|
|
955
|
+
}
|
|
956
|
+
return void 0;
|
|
957
|
+
}
|
|
958
|
+
function readRateLimitHeaders(headers) {
|
|
959
|
+
const snapshot = {};
|
|
960
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
961
|
+
if (remaining !== null) {
|
|
962
|
+
const parsed = Number.parseInt(remaining, 10);
|
|
963
|
+
if (Number.isFinite(parsed)) {
|
|
964
|
+
snapshot.remainingRequests = parsed;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const retryAfter = parseRetryAfterSeconds(headers.get("retry-after"));
|
|
968
|
+
if (retryAfter !== void 0) {
|
|
969
|
+
snapshot.retryAfterSeconds = retryAfter;
|
|
970
|
+
}
|
|
971
|
+
const policy = parseBetaRateLimitPolicy(headers.get("beta-ratelimit-policy"));
|
|
972
|
+
if (policy !== void 0) {
|
|
973
|
+
snapshot.policy = policy;
|
|
974
|
+
}
|
|
975
|
+
const points = parseBetaRateLimit(headers.get("beta-ratelimit"));
|
|
976
|
+
if (points !== void 0) {
|
|
977
|
+
snapshot.remainingPoints = points;
|
|
978
|
+
}
|
|
979
|
+
return snapshot;
|
|
980
|
+
}
|
|
981
|
+
function createRateLimitHeaderMiddleware(config) {
|
|
982
|
+
return async (ctx, next) => {
|
|
983
|
+
const response = await next(ctx);
|
|
984
|
+
const snapshot = readRateLimitHeaders(response.headers);
|
|
985
|
+
if (Object.keys(snapshot).length > 0) {
|
|
986
|
+
try {
|
|
987
|
+
config.onRateLimit(snapshot);
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return response;
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export { CircuitBreaker, CircuitBreakerOpenError, CircuitState, HttpClient, composeMiddleware, createCircuitBreakerMiddleware, createDefaultCircuitBreaker, createHttpClient, createLoggingMiddleware, createRateLimitHeaderMiddleware, createRateLimitMiddleware, createRequestIdMiddleware, createRetryMiddleware, createUserAgentMiddleware, parseBetaRateLimit, parseBetaRateLimitPolicy, parseRetryAfterSeconds, readRateLimitHeaders };
|
|
996
|
+
//# sourceMappingURL=index.js.map
|
|
997
|
+
//# sourceMappingURL=index.js.map
|