hanzio 1.1.0 → 2.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 +832 -219
- package/dist/api-wrapper/builders.d.ts +15 -3
- package/dist/api-wrapper/create-client.d.ts +1 -1
- package/dist/api-wrapper/describe.d.ts +16 -0
- package/dist/api-wrapper/endpoint.d.ts +18 -0
- package/dist/api-wrapper/errors.d.ts +40 -2
- package/dist/api-wrapper/headers.d.ts +2 -1
- package/dist/api-wrapper/index.d.ts +5 -0
- package/dist/api-wrapper/otel.d.ts +44 -0
- package/dist/api-wrapper/paginate.d.ts +12 -0
- package/dist/api-wrapper/redaction.d.ts +2 -0
- package/dist/api-wrapper/shared.d.ts +16 -2
- package/dist/api-wrapper/testing.d.ts +23 -0
- package/dist/api-wrapper/transport.d.ts +12 -10
- package/dist/api-wrapper/types.d.ts +104 -18
- package/dist/array/index.d.ts +50 -4
- package/dist/cache/createCache.d.ts +31 -0
- package/dist/cache/index.d.ts +45 -2
- package/dist/color/contrast.d.ts +40 -0
- package/dist/color/index.d.ts +2 -0
- package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
- package/dist/cool-console-log/index.d.ts +34 -5
- package/dist/crypto/encoding.d.ts +18 -0
- package/dist/crypto/hash.d.ts +33 -0
- package/dist/crypto/index.d.ts +4 -0
- package/dist/crypto/sealer.d.ts +25 -0
- package/dist/crypto/webhook.d.ts +20 -0
- package/dist/date/index.d.ts +46 -2
- package/dist/emitter/index.d.ts +21 -0
- package/dist/error/index.d.ts +12 -0
- package/dist/format/index.d.ts +102 -0
- package/dist/function/index.d.ts +60 -0
- package/dist/guard/index.d.ts +20 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/index-0zvtcbzy.js +224 -0
- package/dist/index-181e6vfw.js +29 -0
- package/dist/index-8r2vdzjg.js +87 -0
- package/dist/index-hy7kbb99.js +157 -0
- package/dist/{src/secrets/loaders.js → index-j1ybktbr.js} +608 -405
- package/dist/index-k9yx6bne.js +89 -0
- package/dist/index-kj1phgrx.js +169 -0
- package/dist/index-mnkcfyx8.js +78 -0
- package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
- package/dist/index-rp5pqghz.js +157 -0
- package/dist/index-srt06xd2.js +15 -0
- package/dist/index-wjyb9bsd.js +263 -0
- package/dist/index.d.ts +7 -7
- package/dist/jwt/index.d.ts +50 -2
- package/dist/math/index.d.ts +2 -2
- package/dist/math/linearRegressionTrend.d.ts +14 -1
- package/dist/math/scale.d.ts +8 -0
- package/dist/object/index.d.ts +19 -0
- package/dist/p-queue/index.d.ts +1 -1
- package/dist/promise/index.d.ts +97 -2
- package/dist/secrets/core.d.ts +47 -0
- package/dist/secrets/deadline.d.ts +6 -0
- package/dist/secrets/environment.d.ts +9 -0
- package/dist/secrets/errors.d.ts +11 -0
- package/dist/secrets/index.d.ts +13 -25
- package/dist/secrets/infisical-cli.d.ts +6 -0
- package/dist/secrets/infisical-config.d.ts +17 -0
- package/dist/secrets/infisical-session.d.ts +4 -0
- package/dist/secrets/infisical.d.ts +8 -13
- package/dist/secrets/loaders.d.ts +5 -12
- package/dist/secrets/vite.d.ts +7 -3
- package/dist/secrets/worker-loader.d.ts +9 -0
- package/dist/secrets/worker.d.ts +3 -0
- package/dist/sitemap/index.d.ts +48 -6
- package/dist/src/api-wrapper/index.js +60 -1100
- package/dist/src/api-wrapper/testing.js +20 -0
- package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
- package/dist/src/crypto/index.js +195 -0
- package/dist/src/format/index.js +211 -0
- package/dist/src/i18n/index.js +36 -0
- package/dist/src/index.js +863 -2809
- package/dist/src/jwt/index.js +138 -92
- package/dist/src/p-queue/index.js +9 -437
- package/dist/src/secrets/index.js +261 -1377
- package/dist/src/secrets/vite.js +6 -17
- package/dist/src/secrets/worker.js +12 -0
- package/dist/src/sitemap/index.js +183 -511
- package/dist/src/storage/index.js +119 -0
- package/dist/src/zod/index.js +347 -174
- package/dist/state/index.d.ts +21 -0
- package/dist/storage/index.d.ts +58 -0
- package/dist/string/index.d.ts +75 -2
- package/dist/typedswitch/index.d.ts +37 -26
- package/dist/types/index.d.ts +11 -0
- package/dist/url/index.d.ts +50 -1
- package/dist/zod/index.d.ts +32 -3
- package/dist/zod/introspect.d.ts +19 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
- package/dist/zod/zodMinMaxFilter.d.ts +22 -3
- package/dist/zod/zodToTypeString.d.ts +8 -3
- package/package.json +87 -7
- package/src/api-wrapper/actions-impl.ts +11 -72
- package/src/api-wrapper/builders.ts +84 -52
- package/src/api-wrapper/create-client.ts +246 -96
- package/src/api-wrapper/describe.ts +157 -0
- package/src/api-wrapper/endpoint.ts +26 -0
- package/src/api-wrapper/error-reporting.ts +7 -1
- package/src/api-wrapper/errors.ts +117 -2
- package/src/api-wrapper/headers.ts +19 -1
- package/src/api-wrapper/index.ts +5 -0
- package/src/api-wrapper/otel.ts +139 -0
- package/src/api-wrapper/paginate.ts +28 -0
- package/src/api-wrapper/redaction.ts +43 -0
- package/src/api-wrapper/shared.ts +21 -2
- package/src/api-wrapper/testing.ts +48 -0
- package/src/api-wrapper/transport.ts +68 -74
- package/src/api-wrapper/types.ts +186 -18
- package/src/array/index.ts +195 -42
- package/src/cache/createCache.ts +157 -0
- package/src/cache/index.ts +235 -83
- package/src/color/contrast.ts +163 -0
- package/src/color/index.ts +22 -0
- package/src/{math → color}/metricsSeriesColors.ts +47 -49
- package/src/cool-console-log/index.ts +128 -41
- package/src/crypto/encoding.ts +83 -0
- package/src/crypto/hash.ts +170 -0
- package/src/crypto/index.ts +26 -0
- package/src/crypto/sealer.ts +168 -0
- package/src/crypto/webhook.ts +86 -0
- package/src/date/index.ts +109 -34
- package/src/emitter/index.ts +82 -0
- package/src/error/index.ts +63 -0
- package/src/format/index.ts +389 -0
- package/src/function/index.ts +201 -0
- package/src/guard/index.ts +80 -0
- package/src/i18n/index.ts +125 -0
- package/src/index.ts +11 -7
- package/src/jwt/index.ts +199 -125
- package/src/math/index.ts +5 -10
- package/src/math/linearRegressionTrend.ts +33 -8
- package/src/math/scale.ts +17 -0
- package/src/object/index.ts +99 -0
- package/src/p-queue/index.ts +2 -0
- package/src/promise/index.ts +304 -9
- package/src/secrets/core.ts +348 -0
- package/src/secrets/deadline.ts +69 -0
- package/src/secrets/environment.ts +69 -0
- package/src/secrets/errors.ts +29 -0
- package/src/secrets/index.ts +79 -210
- package/src/secrets/infisical-cli.ts +99 -0
- package/src/secrets/infisical-config.ts +70 -0
- package/src/secrets/infisical-session.ts +42 -0
- package/src/secrets/infisical.ts +214 -66
- package/src/secrets/loaders.ts +24 -48
- package/src/secrets/vite.ts +44 -15
- package/src/secrets/worker-loader.ts +56 -0
- package/src/secrets/worker.ts +4 -0
- package/src/sitemap/index.ts +293 -130
- package/src/state/index.ts +133 -81
- package/src/storage/index.ts +187 -0
- package/src/string/index.ts +261 -23
- package/src/typedswitch/index.ts +134 -103
- package/src/types/index.ts +14 -0
- package/src/url/index.ts +146 -1
- package/src/zod/index.ts +105 -9
- package/src/zod/introspect.ts +103 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -21
- package/src/zod/zodMinMaxFilter.ts +37 -13
- package/src/zod/zodToTypeString.ts +184 -181
- package/dist/api-wrapper/index.js +0 -250
- package/dist/gzip/index.d.ts +0 -2
- package/dist/gzip/index.js +0 -16
- package/dist/index.js +0 -1823
- package/dist/jwt/index.js +0 -19
- package/dist/sitemap/index.js +0 -552
- package/dist/src/gzip/index.js +0 -16
- package/dist/src/secrets/infisical.js +0 -1194
- package/dist/zod/index.js +0 -210
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCache2
|
|
3
|
+
} from "./index-k9yx6bne.js";
|
|
4
|
+
import {
|
|
5
|
+
ok2,
|
|
6
|
+
err2,
|
|
7
|
+
promiseTimeout2,
|
|
8
|
+
exponentialBackoff2,
|
|
9
|
+
anySignal2
|
|
10
|
+
} from "./index-0zvtcbzy.js";
|
|
11
|
+
import {
|
|
12
|
+
buildQueryString2,
|
|
13
|
+
parseRetryAfter2
|
|
14
|
+
} from "./index-mnkcfyx8.js";
|
|
1
15
|
// src/api-wrapper/headers.ts
|
|
2
16
|
var CANONICAL_HEADER_NAMES = {
|
|
3
17
|
"content-type": "Content-Type",
|
|
@@ -38,13 +52,21 @@ function hasJsonContentType(headers) {
|
|
|
38
52
|
const ct = getHeader(headers, "content-type") ?? "";
|
|
39
53
|
return ct.includes("application/json");
|
|
40
54
|
}
|
|
41
|
-
function buildFetchBody(body, headers) {
|
|
55
|
+
function buildFetchBody(body, headers, configError) {
|
|
42
56
|
if (body === undefined || body === null)
|
|
43
57
|
return;
|
|
44
58
|
if (body instanceof FormData || body instanceof Blob)
|
|
45
59
|
return body;
|
|
46
60
|
if (hasJsonContentType(headers))
|
|
47
61
|
return JSON.stringify(body);
|
|
62
|
+
const isJsonLike = Array.isArray(body) || typeof body === "object" && (Object.getPrototypeOf(body) === Object.prototype || Object.getPrototypeOf(body) === null);
|
|
63
|
+
if (isJsonLike && getHeader(headers, "content-type") === undefined) {
|
|
64
|
+
headers["Content-Type"] = "application/json";
|
|
65
|
+
return JSON.stringify(body);
|
|
66
|
+
}
|
|
67
|
+
if (isJsonLike && configError) {
|
|
68
|
+
throw configError("Request body is a plain object but Content-Type is not application/json");
|
|
69
|
+
}
|
|
48
70
|
return body;
|
|
49
71
|
}
|
|
50
72
|
function getHeadersAsObject(headers) {
|
|
@@ -192,9 +214,12 @@ function tryParseJson(body) {
|
|
|
192
214
|
// src/api-wrapper/errors.ts
|
|
193
215
|
var MAX_BODY_PREVIEW = 500;
|
|
194
216
|
|
|
195
|
-
class
|
|
217
|
+
class ApiError2 extends Error {
|
|
196
218
|
context;
|
|
197
219
|
cause;
|
|
220
|
+
get category() {
|
|
221
|
+
return "unknown";
|
|
222
|
+
}
|
|
198
223
|
constructor(message, context, options) {
|
|
199
224
|
super(message);
|
|
200
225
|
this.name = "ApiError";
|
|
@@ -204,13 +229,14 @@ class ApiError extends Error {
|
|
|
204
229
|
toJSON() {
|
|
205
230
|
return {
|
|
206
231
|
name: this.name,
|
|
232
|
+
category: this.category,
|
|
207
233
|
message: this.message,
|
|
208
234
|
context: this.context
|
|
209
235
|
};
|
|
210
236
|
}
|
|
211
237
|
}
|
|
212
238
|
|
|
213
|
-
class
|
|
239
|
+
class HttpResponseError2 extends ApiError2 {
|
|
214
240
|
status;
|
|
215
241
|
body;
|
|
216
242
|
bodyJson;
|
|
@@ -226,6 +252,9 @@ class HttpResponseError extends ApiError {
|
|
|
226
252
|
this.requestId = getHeader(responseHeaders, "x-request-id") ?? getHeader(responseHeaders, "x-correlation-id");
|
|
227
253
|
this.bodyJson = tryParseJson(body);
|
|
228
254
|
}
|
|
255
|
+
get category() {
|
|
256
|
+
return this.status >= 500 ? "server_error" : "client_error";
|
|
257
|
+
}
|
|
229
258
|
toJSON() {
|
|
230
259
|
return {
|
|
231
260
|
...super.toJSON(),
|
|
@@ -238,25 +267,31 @@ class HttpResponseError extends ApiError {
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
|
|
241
|
-
class
|
|
270
|
+
class ResponseValidationError2 extends ApiError2 {
|
|
242
271
|
zodError;
|
|
243
272
|
rawResponse;
|
|
244
273
|
issues;
|
|
245
274
|
validationIssues;
|
|
246
|
-
|
|
275
|
+
invalidJson;
|
|
276
|
+
constructor(zodError, rawResponse, context, options = {}) {
|
|
247
277
|
const issues = formatZodIssues(zodError, rawResponse);
|
|
248
278
|
const summary = renderIssueSummary(issues);
|
|
249
279
|
super(`Response validation failed (${context.method} ${context.url}):
|
|
250
280
|
${summary}`, context, { cause: zodError });
|
|
251
281
|
this.name = "ResponseValidationError";
|
|
282
|
+
this.invalidJson = options.invalidJson ?? false;
|
|
252
283
|
this.zodError = zodError;
|
|
253
284
|
this.rawResponse = rawResponse;
|
|
254
285
|
this.issues = issues;
|
|
255
286
|
this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
256
287
|
}
|
|
288
|
+
get category() {
|
|
289
|
+
return "output_validation";
|
|
290
|
+
}
|
|
257
291
|
toJSON() {
|
|
258
292
|
return {
|
|
259
293
|
...super.toJSON(),
|
|
294
|
+
invalidJson: this.invalidJson,
|
|
260
295
|
issues: this.issues,
|
|
261
296
|
validationIssues: this.validationIssues,
|
|
262
297
|
rawResponsePreview: previewValue(this.rawResponse, 1000)
|
|
@@ -264,7 +299,7 @@ ${summary}`, context, { cause: zodError });
|
|
|
264
299
|
}
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
class
|
|
302
|
+
class RequestValidationError2 extends ApiError2 {
|
|
268
303
|
zodError;
|
|
269
304
|
target;
|
|
270
305
|
rawInput;
|
|
@@ -282,6 +317,9 @@ ${summary}`, context, { cause: zodError });
|
|
|
282
317
|
this.issues = issues;
|
|
283
318
|
this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
284
319
|
}
|
|
320
|
+
get category() {
|
|
321
|
+
return "input_validation";
|
|
322
|
+
}
|
|
285
323
|
toJSON() {
|
|
286
324
|
return {
|
|
287
325
|
...super.toJSON(),
|
|
@@ -292,86 +330,139 @@ ${summary}`, context, { cause: zodError });
|
|
|
292
330
|
}
|
|
293
331
|
}
|
|
294
332
|
|
|
295
|
-
class
|
|
333
|
+
class RequestTimeoutError2 extends ApiError2 {
|
|
296
334
|
timeoutMs;
|
|
297
335
|
constructor(timeoutMs, context) {
|
|
298
336
|
super(`Request timed out after ${timeoutMs}ms: ${context.method} ${context.url}`, context);
|
|
299
337
|
this.name = "RequestTimeoutError";
|
|
300
338
|
this.timeoutMs = timeoutMs;
|
|
301
339
|
}
|
|
340
|
+
get category() {
|
|
341
|
+
return "timeout";
|
|
342
|
+
}
|
|
302
343
|
}
|
|
303
344
|
|
|
304
|
-
class
|
|
345
|
+
class NetworkError2 extends ApiError2 {
|
|
305
346
|
constructor(message, context, cause) {
|
|
306
347
|
super(message, context, { cause });
|
|
307
348
|
this.name = "NetworkError";
|
|
308
349
|
}
|
|
350
|
+
get category() {
|
|
351
|
+
return "network";
|
|
352
|
+
}
|
|
309
353
|
}
|
|
310
354
|
|
|
311
|
-
class
|
|
355
|
+
class RequestAbortedError2 extends ApiError2 {
|
|
312
356
|
constructor(context, cause) {
|
|
313
357
|
super(`Request aborted: ${context.method} ${context.url}`, context, {
|
|
314
358
|
cause
|
|
315
359
|
});
|
|
316
360
|
this.name = "RequestAbortedError";
|
|
317
361
|
}
|
|
362
|
+
get category() {
|
|
363
|
+
return "aborted";
|
|
364
|
+
}
|
|
318
365
|
}
|
|
319
366
|
|
|
320
|
-
class
|
|
367
|
+
class ConfigError2 extends ApiError2 {
|
|
321
368
|
constructor(message, context) {
|
|
322
369
|
super(message, context);
|
|
323
370
|
this.name = "ConfigError";
|
|
324
371
|
}
|
|
372
|
+
get category() {
|
|
373
|
+
return "config";
|
|
374
|
+
}
|
|
325
375
|
}
|
|
326
376
|
|
|
327
|
-
class
|
|
377
|
+
class ApiResponseError2 extends ApiError2 {
|
|
378
|
+
data;
|
|
379
|
+
constructor(message, data, context, cause) {
|
|
380
|
+
super(message, context, { cause });
|
|
381
|
+
this.name = "ApiResponseError";
|
|
382
|
+
this.data = data;
|
|
383
|
+
}
|
|
384
|
+
get category() {
|
|
385
|
+
return "response_rejected";
|
|
386
|
+
}
|
|
387
|
+
toJSON() {
|
|
388
|
+
return {
|
|
389
|
+
...super.toJSON(),
|
|
390
|
+
dataPreview: previewValue(this.data, 1000)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
class ActionError2 extends ApiError2 {
|
|
328
396
|
constructor(message, context, cause) {
|
|
329
397
|
super(message, context, { cause });
|
|
330
398
|
this.name = "ActionError";
|
|
331
399
|
}
|
|
400
|
+
get category() {
|
|
401
|
+
return "action";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
var isApiError2 = (e) => e instanceof ApiError2;
|
|
405
|
+
var isHttpResponseError2 = (e) => e instanceof HttpResponseError2;
|
|
406
|
+
var isResponseValidationError2 = (e) => e instanceof ResponseValidationError2;
|
|
407
|
+
var isRequestValidationError2 = (e) => e instanceof RequestValidationError2;
|
|
408
|
+
var isRequestTimeoutError2 = (e) => e instanceof RequestTimeoutError2;
|
|
409
|
+
var isNetworkError2 = (e) => e instanceof NetworkError2;
|
|
410
|
+
var isRequestAbortedError2 = (e) => e instanceof RequestAbortedError2;
|
|
411
|
+
var isConfigError2 = (e) => e instanceof ConfigError2;
|
|
412
|
+
var isActionError2 = (e) => e instanceof ActionError2;
|
|
413
|
+
var isApiResponseError2 = (e) => e instanceof ApiResponseError2;
|
|
414
|
+
function isNonRetryableApiError2(error) {
|
|
415
|
+
return error instanceof HttpResponseError2 || error instanceof ResponseValidationError2 || error instanceof ApiResponseError2 || error instanceof RequestValidationError2 || error instanceof ConfigError2 || error instanceof RequestAbortedError2;
|
|
332
416
|
}
|
|
333
|
-
var isApiError = (e) => e instanceof ApiError;
|
|
334
|
-
var isHttpResponseError = (e) => e instanceof HttpResponseError;
|
|
335
|
-
var isResponseValidationError = (e) => e instanceof ResponseValidationError;
|
|
336
|
-
var isRequestValidationError = (e) => e instanceof RequestValidationError;
|
|
337
|
-
var isRequestTimeoutError = (e) => e instanceof RequestTimeoutError;
|
|
338
|
-
var isNetworkError = (e) => e instanceof NetworkError;
|
|
339
|
-
var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
|
|
340
|
-
var isConfigError = (e) => e instanceof ConfigError;
|
|
341
|
-
var isActionError = (e) => e instanceof ActionError;
|
|
342
417
|
// src/api-wrapper/builders.ts
|
|
343
418
|
import { ZodError as ZodError2 } from "zod";
|
|
344
|
-
function buildUrl(
|
|
419
|
+
function buildUrl(options) {
|
|
420
|
+
const {
|
|
421
|
+
endpoint,
|
|
422
|
+
params,
|
|
423
|
+
queryParams,
|
|
424
|
+
baseApiUrls,
|
|
425
|
+
defaultBaseApiUrl,
|
|
426
|
+
url,
|
|
427
|
+
doNotEncodeQueryParams,
|
|
428
|
+
configError
|
|
429
|
+
} = options;
|
|
430
|
+
let finalUrl;
|
|
345
431
|
if (url) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
throw configError(`Unknown base API URL: ${baseUrlKey}`);
|
|
432
|
+
finalUrl = replaceUrlPathParams(url, params, configError);
|
|
433
|
+
} else {
|
|
434
|
+
const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
|
|
435
|
+
if (!defaultBaseUrl) {
|
|
436
|
+
throw configError("At least one base API URL is required");
|
|
437
|
+
}
|
|
438
|
+
const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
|
|
439
|
+
const configBaseUrl = baseApiUrls[baseUrlKey];
|
|
440
|
+
if (!configBaseUrl) {
|
|
441
|
+
throw configError(`Unknown base API URL: ${baseUrlKey}`);
|
|
442
|
+
}
|
|
443
|
+
const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
|
|
444
|
+
const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
|
|
445
|
+
finalUrl = joinUrlPath(baseUrl, replacePathParams(path, params, configError));
|
|
361
446
|
}
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const queryString = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
|
|
447
|
+
const queryString = buildQueryString2(queryParams, {
|
|
448
|
+
encode: !doNotEncodeQueryParams
|
|
449
|
+
});
|
|
366
450
|
return {
|
|
367
451
|
finalUrl,
|
|
368
452
|
fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
|
|
369
453
|
};
|
|
370
454
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
455
|
+
function buildPathParams(endpoint, params, ctx) {
|
|
456
|
+
if (!endpoint.reqParamsSchema)
|
|
457
|
+
return params;
|
|
458
|
+
try {
|
|
459
|
+
return endpoint.reqParamsSchema.parse(params);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof ZodError2) {
|
|
462
|
+
throw new RequestValidationError2(error, "params", params, ctx());
|
|
463
|
+
}
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
375
466
|
}
|
|
376
467
|
function buildQueryParams(endpoint, query, ctx) {
|
|
377
468
|
const defaultParams = endpoint.reqDefaultQueryParams ?? {};
|
|
@@ -381,15 +472,14 @@ function buildQueryParams(endpoint, query, ctx) {
|
|
|
381
472
|
parsedQuery = endpoint.reqQuerySchema.parse(query);
|
|
382
473
|
} catch (error) {
|
|
383
474
|
if (error instanceof ZodError2) {
|
|
384
|
-
throw new
|
|
475
|
+
throw new RequestValidationError2(error, "query", query, ctx());
|
|
385
476
|
}
|
|
386
477
|
throw error;
|
|
387
478
|
}
|
|
388
479
|
} else {
|
|
389
480
|
parsedQuery = query ?? {};
|
|
390
481
|
}
|
|
391
|
-
|
|
392
|
-
return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
|
|
482
|
+
return { ...defaultParams, ...parsedQuery };
|
|
393
483
|
}
|
|
394
484
|
function buildRequestBody(endpoint, body, ctx) {
|
|
395
485
|
if (!endpoint.reqBodySchema)
|
|
@@ -399,7 +489,7 @@ function buildRequestBody(endpoint, body, ctx) {
|
|
|
399
489
|
parsedBody = endpoint.reqBodySchema.parse(body);
|
|
400
490
|
} catch (error) {
|
|
401
491
|
if (error instanceof ZodError2) {
|
|
402
|
-
throw new
|
|
492
|
+
throw new RequestValidationError2(error, "body", body, ctx());
|
|
403
493
|
}
|
|
404
494
|
throw error;
|
|
405
495
|
}
|
|
@@ -422,7 +512,7 @@ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
|
|
|
422
512
|
parsedHeaders = endpoint.reqHeadersSchema.parse(headers);
|
|
423
513
|
} catch (error) {
|
|
424
514
|
if (error instanceof ZodError2) {
|
|
425
|
-
throw new
|
|
515
|
+
throw new RequestValidationError2(error, "headers", headers, ctx());
|
|
426
516
|
}
|
|
427
517
|
throw error;
|
|
428
518
|
}
|
|
@@ -436,8 +526,20 @@ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
|
|
|
436
526
|
...parsedHeaders
|
|
437
527
|
});
|
|
438
528
|
}
|
|
529
|
+
function joinUrlPath(base, path) {
|
|
530
|
+
if (!path || path.startsWith("?"))
|
|
531
|
+
return `${base}${path}`;
|
|
532
|
+
return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
533
|
+
}
|
|
534
|
+
function replaceUrlPathParams(url, params, configError) {
|
|
535
|
+
const origin = url.match(/^[a-z][a-z\d+.-]*:\/\/[^/?#]*/i)?.[0] ?? "";
|
|
536
|
+
const rest = url.slice(origin.length);
|
|
537
|
+
const suffixIndex = rest.search(/[?#]/);
|
|
538
|
+
const path = suffixIndex === -1 ? rest : rest.slice(0, suffixIndex);
|
|
539
|
+
return `${origin}${replacePathParams(path, params, configError)}${rest.slice(path.length)}`;
|
|
540
|
+
}
|
|
439
541
|
function replacePathParams(path, params, configError) {
|
|
440
|
-
return path.replace(/:(\w
|
|
542
|
+
return path.replace(/:([A-Za-z_]\w*)/g, (_, key) => {
|
|
441
543
|
const value = params?.[key];
|
|
442
544
|
if (value === undefined) {
|
|
443
545
|
throw configError(`Missing required path parameter: ${key}`);
|
|
@@ -448,76 +550,144 @@ function replacePathParams(path, params, configError) {
|
|
|
448
550
|
|
|
449
551
|
// src/api-wrapper/actions-impl.ts
|
|
450
552
|
function createActionCache() {
|
|
451
|
-
const store =
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return existing.inflight;
|
|
458
|
-
if (existing.hasValue)
|
|
459
|
-
return existing.value;
|
|
460
|
-
}
|
|
461
|
-
const promise = (async () => fn())();
|
|
462
|
-
const entry = {
|
|
463
|
-
hasValue: false,
|
|
464
|
-
inflight: promise,
|
|
465
|
-
expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
466
|
-
};
|
|
467
|
-
store.set(key, entry);
|
|
468
|
-
try {
|
|
469
|
-
const value = await promise;
|
|
470
|
-
entry.value = value;
|
|
471
|
-
entry.hasValue = true;
|
|
472
|
-
entry.inflight = undefined;
|
|
473
|
-
return value;
|
|
474
|
-
} catch (error) {
|
|
475
|
-
store.delete(key);
|
|
476
|
-
throw error;
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
cache.get = (key) => {
|
|
480
|
-
const entry = store.get(key);
|
|
481
|
-
if (!entry || !entry.hasValue)
|
|
482
|
-
return;
|
|
483
|
-
if (isExpired(entry)) {
|
|
484
|
-
store.delete(key);
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
return entry.value;
|
|
488
|
-
};
|
|
489
|
-
cache.set = (key, value, options) => {
|
|
490
|
-
store.set(key, {
|
|
491
|
-
value,
|
|
492
|
-
hasValue: true,
|
|
493
|
-
expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
494
|
-
});
|
|
495
|
-
};
|
|
496
|
-
cache.invalidate = (key) => {
|
|
497
|
-
store.delete(key);
|
|
498
|
-
};
|
|
499
|
-
cache.clear = () => {
|
|
500
|
-
store.clear();
|
|
501
|
-
};
|
|
553
|
+
const store = createCache2();
|
|
554
|
+
const cache = (key, fn, options) => store.getOrSet(key, fn, options);
|
|
555
|
+
cache.get = (key) => store.get(key);
|
|
556
|
+
cache.set = (key, value, options) => store.set(key, value, options);
|
|
557
|
+
cache.invalidate = (key) => store.delete(key);
|
|
558
|
+
cache.clear = () => store.clear();
|
|
502
559
|
return cache;
|
|
503
560
|
}
|
|
504
561
|
function makeDefineAction() {
|
|
505
562
|
function defineAction(...args) {
|
|
506
563
|
if (args.length === 0) {
|
|
507
|
-
return (
|
|
508
|
-
...
|
|
564
|
+
return (def) => ({
|
|
565
|
+
...def,
|
|
509
566
|
noRuntimeInput: false
|
|
510
567
|
});
|
|
511
568
|
}
|
|
512
569
|
const def = args[0];
|
|
513
570
|
return {
|
|
514
571
|
...def,
|
|
515
|
-
noRuntimeInput:
|
|
572
|
+
noRuntimeInput: def.input === undefined
|
|
516
573
|
};
|
|
517
574
|
}
|
|
518
575
|
return defineAction;
|
|
519
576
|
}
|
|
520
|
-
var
|
|
577
|
+
var defineAction2 = makeDefineAction();
|
|
578
|
+
|
|
579
|
+
// src/api-wrapper/describe.ts
|
|
580
|
+
var BODY_PREVIEW = 500;
|
|
581
|
+
var schemaByTarget = {
|
|
582
|
+
body: "reqBodySchema",
|
|
583
|
+
query: "reqQuerySchema",
|
|
584
|
+
params: "reqParamsSchema",
|
|
585
|
+
headers: "reqHeadersSchema",
|
|
586
|
+
input: "the action input schema"
|
|
587
|
+
};
|
|
588
|
+
function headline(error) {
|
|
589
|
+
if (error instanceof HttpResponseError2) {
|
|
590
|
+
const status = `HTTP ${error.status}`;
|
|
591
|
+
if (error.status >= 500) {
|
|
592
|
+
return `server error (${status}): the API failed while handling the request`;
|
|
593
|
+
}
|
|
594
|
+
if (error.status === 401 || error.status === 403) {
|
|
595
|
+
return `request rejected (${status}): check credentials and permissions`;
|
|
596
|
+
}
|
|
597
|
+
if (error.status === 404) {
|
|
598
|
+
return `request rejected (${status}): check the path and the IDs in it`;
|
|
599
|
+
}
|
|
600
|
+
if (error.status === 429)
|
|
601
|
+
return `rate limited (${status})`;
|
|
602
|
+
return `request rejected (${status}): check params, query and body`;
|
|
603
|
+
}
|
|
604
|
+
if (error instanceof RequestValidationError2) {
|
|
605
|
+
return `input validation failed: the caller's ${error.target} doesn't match ${schemaByTarget[error.target]} (nothing was sent)`;
|
|
606
|
+
}
|
|
607
|
+
if (error instanceof ResponseValidationError2) {
|
|
608
|
+
return error.invalidJson ? "output validation failed: the response body is not valid JSON" : "output validation failed: the response doesn't match resSchema";
|
|
609
|
+
}
|
|
610
|
+
if (error instanceof ApiResponseError2) {
|
|
611
|
+
return `response rejected by checkResponse: ${error.message}`;
|
|
612
|
+
}
|
|
613
|
+
if (error instanceof RequestTimeoutError2) {
|
|
614
|
+
return `timed out after ${error.timeoutMs}ms`;
|
|
615
|
+
}
|
|
616
|
+
const labels = {
|
|
617
|
+
network: `network error: couldn't reach the API (${error.message})`,
|
|
618
|
+
aborted: "aborted by the caller",
|
|
619
|
+
config: `configuration error: ${error.message}`,
|
|
620
|
+
action: `action failed: ${error.message}`
|
|
621
|
+
};
|
|
622
|
+
return labels[error.category] ?? error.message;
|
|
623
|
+
}
|
|
624
|
+
function details(error) {
|
|
625
|
+
if (error instanceof HttpResponseError2) {
|
|
626
|
+
const body = error.bodyJson === undefined ? error.body : previewValue(error.bodyJson, BODY_PREVIEW);
|
|
627
|
+
return body ? [
|
|
628
|
+
` response: ${body.length > BODY_PREVIEW ? `${body.slice(0, BODY_PREVIEW)}…` : body}`
|
|
629
|
+
] : [];
|
|
630
|
+
}
|
|
631
|
+
if (error instanceof ResponseValidationError2 || error instanceof RequestValidationError2) {
|
|
632
|
+
const lines = [renderIssueSummary(error.issues)];
|
|
633
|
+
if (error instanceof ResponseValidationError2 && !error.invalidJson) {
|
|
634
|
+
lines.push(" fix: update resSchema (or resFormatter), or mark changed fields optional");
|
|
635
|
+
}
|
|
636
|
+
return lines;
|
|
637
|
+
}
|
|
638
|
+
if (error instanceof ApiResponseError2) {
|
|
639
|
+
return [` response: ${previewValue(error.data, BODY_PREVIEW)}`];
|
|
640
|
+
}
|
|
641
|
+
if (error instanceof ActionError2 && error.cause instanceof Error) {
|
|
642
|
+
return error.cause.stack ? [` cause: ${error.cause.stack}`] : [];
|
|
643
|
+
}
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
function describeApiError2(error) {
|
|
647
|
+
const { context } = error;
|
|
648
|
+
const sent = error.category !== "input_validation" && error.category !== "config";
|
|
649
|
+
const meta = [
|
|
650
|
+
context.url ? `${context.method} ${context.url}` : context.method,
|
|
651
|
+
...sent ? [`attempt ${context.attempt + 1}/${context.maxRetries + 1}`] : [],
|
|
652
|
+
`${context.elapsedMs}ms`
|
|
653
|
+
];
|
|
654
|
+
if (error instanceof HttpResponseError2 && error.requestId) {
|
|
655
|
+
meta.push(`request id ${error.requestId}`);
|
|
656
|
+
}
|
|
657
|
+
return [
|
|
658
|
+
`✗ ${context.endpoint} failed: ${headline(error)}`,
|
|
659
|
+
` ${meta.join(" · ")}`,
|
|
660
|
+
...details(error)
|
|
661
|
+
].join(`
|
|
662
|
+
`);
|
|
663
|
+
}
|
|
664
|
+
function apiErrorLogAttributes2(error) {
|
|
665
|
+
const { context } = error;
|
|
666
|
+
const attributes = {
|
|
667
|
+
errorName: error.name,
|
|
668
|
+
category: error.category,
|
|
669
|
+
endpoint: context.endpoint,
|
|
670
|
+
method: context.method,
|
|
671
|
+
url: context.url,
|
|
672
|
+
attempt: context.attempt,
|
|
673
|
+
maxRetries: context.maxRetries,
|
|
674
|
+
elapsedMs: context.elapsedMs
|
|
675
|
+
};
|
|
676
|
+
if (error instanceof HttpResponseError2) {
|
|
677
|
+
attributes.status = error.status;
|
|
678
|
+
if (error.requestId)
|
|
679
|
+
attributes.requestId = error.requestId;
|
|
680
|
+
}
|
|
681
|
+
if (error instanceof ResponseValidationError2 || error instanceof RequestValidationError2) {
|
|
682
|
+
attributes.issueCount = error.issues.length;
|
|
683
|
+
attributes.issues = error.validationIssues;
|
|
684
|
+
}
|
|
685
|
+
if (error instanceof RequestValidationError2)
|
|
686
|
+
attributes.target = error.target;
|
|
687
|
+
if (error instanceof RequestTimeoutError2)
|
|
688
|
+
attributes.timeoutMs = error.timeoutMs;
|
|
689
|
+
return attributes;
|
|
690
|
+
}
|
|
521
691
|
|
|
522
692
|
// src/api-wrapper/redaction.ts
|
|
523
693
|
var SENSITIVE_HEADER_KEYS = new Set([
|
|
@@ -536,9 +706,20 @@ var SENSITIVE_BODY_KEYS = new Set([
|
|
|
536
706
|
"refresh_token",
|
|
537
707
|
"secret",
|
|
538
708
|
"client_secret",
|
|
709
|
+
"clientsecret",
|
|
539
710
|
"apikey",
|
|
540
711
|
"api_key"
|
|
541
712
|
]);
|
|
713
|
+
var SENSITIVE_QUERY_KEYS = new Set([
|
|
714
|
+
...SENSITIVE_BODY_KEYS,
|
|
715
|
+
"key",
|
|
716
|
+
"code",
|
|
717
|
+
"sig",
|
|
718
|
+
"signature",
|
|
719
|
+
"x-amz-signature",
|
|
720
|
+
"x-amz-credential",
|
|
721
|
+
"x-amz-security-token"
|
|
722
|
+
]);
|
|
542
723
|
function redactHeaders(headers) {
|
|
543
724
|
if (!headers)
|
|
544
725
|
return;
|
|
@@ -567,9 +748,29 @@ function redactBody(body) {
|
|
|
567
748
|
}
|
|
568
749
|
return out;
|
|
569
750
|
}
|
|
751
|
+
function redactUrl(url) {
|
|
752
|
+
const queryStart = url.indexOf("?");
|
|
753
|
+
if (queryStart === -1)
|
|
754
|
+
return url;
|
|
755
|
+
const hashStart = url.indexOf("#", queryStart);
|
|
756
|
+
const query = url.slice(queryStart + 1, hashStart === -1 ? undefined : hashStart);
|
|
757
|
+
const redacted = query.split("&").map((pair) => {
|
|
758
|
+
const [key = ""] = pair.split("=", 1);
|
|
759
|
+
return SENSITIVE_QUERY_KEYS.has(decodeKey(key).toLowerCase()) ? `${key}=[REDACTED]` : pair;
|
|
760
|
+
}).join("&");
|
|
761
|
+
return `${url.slice(0, queryStart + 1)}${redacted}${hashStart === -1 ? "" : url.slice(hashStart)}`;
|
|
762
|
+
}
|
|
763
|
+
function decodeKey(key) {
|
|
764
|
+
try {
|
|
765
|
+
return decodeURIComponent(key.replace(/\+/g, " "));
|
|
766
|
+
} catch {
|
|
767
|
+
return key;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
570
770
|
function defaultRedactContext(ctx) {
|
|
571
771
|
return {
|
|
572
772
|
...ctx,
|
|
773
|
+
url: redactUrl(ctx.url),
|
|
573
774
|
requestHeaders: redactHeaders(ctx.requestHeaders),
|
|
574
775
|
requestBody: redactBody(ctx.requestBody),
|
|
575
776
|
meta: ctx.meta
|
|
@@ -582,11 +783,15 @@ function applyErrorContextRedaction(ctx, config) {
|
|
|
582
783
|
return config.redact ? config.redact(base) : base;
|
|
583
784
|
}
|
|
584
785
|
async function reportApiError(err, config) {
|
|
786
|
+
const rawUrl = err.context.url;
|
|
585
787
|
err.context = applyErrorContextRedaction(err.context, config);
|
|
788
|
+
if (rawUrl && rawUrl !== err.context.url) {
|
|
789
|
+
err.message = err.message.split(rawUrl).join(err.context.url);
|
|
790
|
+
}
|
|
586
791
|
try {
|
|
587
792
|
await config.onError?.(err);
|
|
588
793
|
} catch {}
|
|
589
|
-
config.logger?.error?.(err);
|
|
794
|
+
config.logger?.error?.(describeApiError2(err), apiErrorLogAttributes2(err));
|
|
590
795
|
return err;
|
|
591
796
|
}
|
|
592
797
|
|
|
@@ -620,35 +825,35 @@ async function validateResponse(schema, data, ctx) {
|
|
|
620
825
|
return await schema.parseAsync(data);
|
|
621
826
|
} catch (error) {
|
|
622
827
|
if (error instanceof ZodError3) {
|
|
623
|
-
throw new
|
|
828
|
+
throw new ResponseValidationError2(error, data, ctx());
|
|
624
829
|
}
|
|
625
830
|
throw error;
|
|
626
831
|
}
|
|
627
832
|
}
|
|
628
833
|
async function httpRequest(config, ctx) {
|
|
629
|
-
const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
|
|
630
|
-
const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
|
|
631
834
|
const timeoutController = new AbortController;
|
|
632
835
|
const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
|
|
633
|
-
const signal =
|
|
836
|
+
const signal = anySignal2(timeoutController.signal, config.userSignal);
|
|
634
837
|
try {
|
|
635
|
-
return await config.fetchFn(fullUrl, {
|
|
838
|
+
return await config.fetchFn(config.fullUrl, {
|
|
636
839
|
method: config.method,
|
|
637
840
|
headers: config.headers,
|
|
638
|
-
body: buildFetchBody(config.body, config.headers),
|
|
841
|
+
body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError2(msg, ctx())),
|
|
639
842
|
signal
|
|
640
843
|
});
|
|
641
844
|
} catch (error) {
|
|
642
845
|
if (isDomAbortError(error)) {
|
|
643
846
|
if (config.userSignal?.aborted) {
|
|
644
|
-
throw new
|
|
847
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
645
848
|
}
|
|
646
849
|
if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
|
|
647
|
-
throw new
|
|
850
|
+
throw new RequestTimeoutError2(config.timeoutMs, ctx());
|
|
648
851
|
}
|
|
649
|
-
throw new
|
|
852
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
650
853
|
}
|
|
651
|
-
|
|
854
|
+
if (error instanceof ConfigError2)
|
|
855
|
+
throw error;
|
|
856
|
+
throw new NetworkError2(error instanceof Error ? error.message : String(error), ctx(), error);
|
|
652
857
|
} finally {
|
|
653
858
|
if (timeout)
|
|
654
859
|
clearTimeout(timeout);
|
|
@@ -657,24 +862,6 @@ async function httpRequest(config, ctx) {
|
|
|
657
862
|
function isDomAbortError(error) {
|
|
658
863
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
659
864
|
}
|
|
660
|
-
function mergeSignals(a, b) {
|
|
661
|
-
if (!b)
|
|
662
|
-
return a;
|
|
663
|
-
const anyFn = AbortSignal.any;
|
|
664
|
-
if (typeof anyFn === "function")
|
|
665
|
-
return anyFn([a, b]);
|
|
666
|
-
const controller = new AbortController;
|
|
667
|
-
const onAbort = () => controller.abort();
|
|
668
|
-
if (a.aborted)
|
|
669
|
-
controller.abort();
|
|
670
|
-
else
|
|
671
|
-
a.addEventListener("abort", onAbort, { once: true });
|
|
672
|
-
if (b.aborted)
|
|
673
|
-
controller.abort();
|
|
674
|
-
else
|
|
675
|
-
b.addEventListener("abort", onAbort, { once: true });
|
|
676
|
-
return controller.signal;
|
|
677
|
-
}
|
|
678
865
|
async function readErrorResponse(response) {
|
|
679
866
|
const body = await response.text();
|
|
680
867
|
return {
|
|
@@ -684,35 +871,18 @@ async function readErrorResponse(response) {
|
|
|
684
871
|
};
|
|
685
872
|
}
|
|
686
873
|
function getRetryDelay(params) {
|
|
874
|
+
const maxDelay = params.maxRetryDelayMs ?? 30000;
|
|
687
875
|
const { retryDelay, attempt, response } = params;
|
|
688
876
|
if (response && (response.status === 429 || response.status === 503)) {
|
|
689
|
-
const
|
|
690
|
-
if (
|
|
691
|
-
|
|
692
|
-
if (Number.isFinite(seconds))
|
|
693
|
-
return seconds * 1000;
|
|
694
|
-
const dateMs = Date.parse(retryAfter);
|
|
695
|
-
if (!Number.isNaN(dateMs)) {
|
|
696
|
-
return Math.max(0, dateMs - Date.now());
|
|
697
|
-
}
|
|
698
|
-
}
|
|
877
|
+
const retryAfterMs = parseRetryAfter2(response.headers.get("retry-after"));
|
|
878
|
+
if (retryAfterMs !== undefined)
|
|
879
|
+
return Math.min(retryAfterMs, maxDelay);
|
|
699
880
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
return retryDelay;
|
|
881
|
+
const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
|
|
882
|
+
return Math.min(delayMs, maxDelay);
|
|
703
883
|
}
|
|
704
884
|
function shouldAttemptRetryOnError(error) {
|
|
705
|
-
|
|
706
|
-
return false;
|
|
707
|
-
if (error instanceof ResponseValidationError)
|
|
708
|
-
return false;
|
|
709
|
-
if (error instanceof RequestValidationError)
|
|
710
|
-
return false;
|
|
711
|
-
if (error instanceof ConfigError)
|
|
712
|
-
return false;
|
|
713
|
-
if (error instanceof RequestAbortedError)
|
|
714
|
-
return false;
|
|
715
|
-
return true;
|
|
885
|
+
return !isNonRetryableApiError2(error);
|
|
716
886
|
}
|
|
717
887
|
function shouldAttemptRetryOnHttp(retryCtx) {
|
|
718
888
|
const { error, response } = retryCtx;
|
|
@@ -725,6 +895,7 @@ async function makeRequestWithRetry(args) {
|
|
|
725
895
|
config,
|
|
726
896
|
maxRetries,
|
|
727
897
|
retryDelay,
|
|
898
|
+
maxRetryDelayMs,
|
|
728
899
|
validateFn,
|
|
729
900
|
shouldRetry,
|
|
730
901
|
logger,
|
|
@@ -740,7 +911,12 @@ async function makeRequestWithRetry(args) {
|
|
|
740
911
|
while (true) {
|
|
741
912
|
let response;
|
|
742
913
|
try {
|
|
743
|
-
logger?.debug?.("API request",
|
|
914
|
+
logger?.debug?.("API request", {
|
|
915
|
+
endpoint: endpointName,
|
|
916
|
+
method,
|
|
917
|
+
url: redactUrl(fullUrl),
|
|
918
|
+
attempt: retryCount
|
|
919
|
+
});
|
|
744
920
|
response = await httpRequest(config, (over) => ctx({ attempt: retryCount, ...over }));
|
|
745
921
|
await onResponse?.({
|
|
746
922
|
endpoint: endpointName,
|
|
@@ -763,27 +939,28 @@ async function makeRequestWithRetry(args) {
|
|
|
763
939
|
const delayMs = getRetryDelay({
|
|
764
940
|
retryDelay,
|
|
765
941
|
attempt: retryCount,
|
|
766
|
-
response
|
|
942
|
+
response,
|
|
943
|
+
maxRetryDelayMs
|
|
767
944
|
});
|
|
768
945
|
await onRetry?.({
|
|
769
946
|
...retryCtx,
|
|
770
947
|
delayMs,
|
|
771
948
|
nextAttempt: retryCount + 1
|
|
772
949
|
});
|
|
950
|
+
await sleepBeforeRetry(delayMs, config.userSignal, () => ctx({ attempt: retryCount }));
|
|
773
951
|
retryCount++;
|
|
774
|
-
await delay(delayMs);
|
|
775
952
|
continue;
|
|
776
953
|
}
|
|
777
954
|
const { body, headers: responseHeaders } = await readErrorResponse(response);
|
|
778
|
-
throw new
|
|
955
|
+
throw new HttpResponseError2(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
|
|
779
956
|
}
|
|
780
957
|
return {
|
|
781
|
-
validatedData: await validateFn(response),
|
|
958
|
+
validatedData: await validateFn(response, retryCount),
|
|
782
959
|
retryCount,
|
|
783
960
|
httpStatus: response.status
|
|
784
961
|
};
|
|
785
962
|
} catch (error) {
|
|
786
|
-
if (error
|
|
963
|
+
if (isNonRetryableApiError2(error) && !(error instanceof ApiResponseError2)) {
|
|
787
964
|
throw error;
|
|
788
965
|
}
|
|
789
966
|
const retryCtx = {
|
|
@@ -800,41 +977,48 @@ async function makeRequestWithRetry(args) {
|
|
|
800
977
|
const delayMs = getRetryDelay({
|
|
801
978
|
retryDelay,
|
|
802
979
|
attempt: retryCount,
|
|
803
|
-
response: undefined
|
|
980
|
+
response: undefined,
|
|
981
|
+
maxRetryDelayMs
|
|
804
982
|
});
|
|
805
983
|
await onRetry?.({
|
|
806
984
|
...retryCtx,
|
|
807
985
|
delayMs,
|
|
808
986
|
nextAttempt: retryCount + 1
|
|
809
987
|
});
|
|
988
|
+
await sleepBeforeRetry(delayMs, config.userSignal, () => ctx({ attempt: retryCount }));
|
|
810
989
|
retryCount++;
|
|
811
|
-
await delay(delayMs);
|
|
812
990
|
}
|
|
813
991
|
}
|
|
814
992
|
}
|
|
815
993
|
async function validateAndTransformResponse(endpoint, response, ctx) {
|
|
816
994
|
const responseHeaders = getHeadersAsObject(response.headers);
|
|
995
|
+
const text = await response.text();
|
|
996
|
+
const responseSizeBytes = new TextEncoder().encode(text).length;
|
|
817
997
|
let responseData;
|
|
818
998
|
if (hasJsonContentType(responseHeaders)) {
|
|
819
|
-
const text = await response.text();
|
|
820
999
|
try {
|
|
821
1000
|
responseData = text === "" ? undefined : JSON.parse(text);
|
|
822
1001
|
} catch (error) {
|
|
823
|
-
throw new
|
|
1002
|
+
throw new ResponseValidationError2(makeJsonParseZodError(error, text), text, ctx(), { invalidJson: true });
|
|
824
1003
|
}
|
|
825
1004
|
} else {
|
|
826
|
-
responseData =
|
|
1005
|
+
responseData = text;
|
|
827
1006
|
}
|
|
828
1007
|
const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
|
|
829
|
-
|
|
1008
|
+
const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
|
|
1009
|
+
return { data, responseSizeBytes };
|
|
830
1010
|
}
|
|
831
|
-
function
|
|
1011
|
+
function defaultShouldRetry2(ctx) {
|
|
832
1012
|
if (ctx.error)
|
|
833
1013
|
return shouldAttemptRetryOnError(ctx.error);
|
|
834
1014
|
return shouldAttemptRetryOnHttp(ctx);
|
|
835
1015
|
}
|
|
836
|
-
function
|
|
837
|
-
|
|
1016
|
+
async function sleepBeforeRetry(ms, signal, ctx) {
|
|
1017
|
+
try {
|
|
1018
|
+
await promiseTimeout2(ms, { signal });
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
1021
|
+
}
|
|
838
1022
|
}
|
|
839
1023
|
|
|
840
1024
|
// src/api-wrapper/create-client.ts
|
|
@@ -845,6 +1029,28 @@ function isActionInvokeOptionsOnly(value) {
|
|
|
845
1029
|
return false;
|
|
846
1030
|
return Object.keys(value).every((k) => k === "signal" || k === "meta");
|
|
847
1031
|
}
|
|
1032
|
+
function resolveActionInvocation(action, actionName, first, second) {
|
|
1033
|
+
if (!action.noRuntimeInput) {
|
|
1034
|
+
return { input: first, options: second ?? {} };
|
|
1035
|
+
}
|
|
1036
|
+
if (first === undefined || first === null) {
|
|
1037
|
+
return { input: undefined, options: second ?? {} };
|
|
1038
|
+
}
|
|
1039
|
+
if (second !== undefined) {
|
|
1040
|
+
return { input: undefined, options: second };
|
|
1041
|
+
}
|
|
1042
|
+
if (isActionInvokeOptionsOnly(first)) {
|
|
1043
|
+
return { input: undefined, options: first };
|
|
1044
|
+
}
|
|
1045
|
+
throw new ConfigError2(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
|
|
1046
|
+
endpoint: actionName,
|
|
1047
|
+
method: "GET",
|
|
1048
|
+
url: "",
|
|
1049
|
+
attempt: 0,
|
|
1050
|
+
maxRetries: 0,
|
|
1051
|
+
elapsedMs: 0
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
848
1054
|
function createApiClientImpl(apiConfig) {
|
|
849
1055
|
let actions = {};
|
|
850
1056
|
const cache = createActionCache();
|
|
@@ -870,37 +1076,49 @@ function createApiClientImpl(apiConfig) {
|
|
|
870
1076
|
...over
|
|
871
1077
|
});
|
|
872
1078
|
if (!endpoint) {
|
|
873
|
-
throw await reportApiError(new
|
|
1079
|
+
throw await reportApiError(new ConfigError2(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
|
|
874
1080
|
}
|
|
875
1081
|
baseContext.method = endpoint.method;
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const built = buildUrl(endpoint, rawInput.reqParams, rawInput.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url, (msg) => new ConfigError(msg, ctx()));
|
|
880
|
-
finalUrl = built.finalUrl;
|
|
881
|
-
fullUrl = built.fullUrl;
|
|
882
|
-
} catch (e) {
|
|
883
|
-
if (e instanceof ApiError)
|
|
884
|
-
throw await reportApiError(e, apiConfig);
|
|
885
|
-
throw e;
|
|
886
|
-
}
|
|
887
|
-
baseContext.url = fullUrl;
|
|
1082
|
+
if (typeof endpoint.path === "string")
|
|
1083
|
+
baseContext.url = endpoint.path;
|
|
1084
|
+
let pathParams;
|
|
888
1085
|
let queryParams;
|
|
889
1086
|
let body;
|
|
890
1087
|
let headers;
|
|
891
1088
|
try {
|
|
1089
|
+
pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
|
|
892
1090
|
queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
|
|
893
1091
|
body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
|
|
894
|
-
|
|
1092
|
+
const defaultHeaders = typeof apiConfig.defaultHeaders === "function" ? await apiConfig.defaultHeaders() : apiConfig.defaultHeaders;
|
|
1093
|
+
headers = buildHeaders(endpoint, rawInput.reqHeaders, defaultHeaders ?? {}, ctx);
|
|
895
1094
|
} catch (e) {
|
|
896
|
-
if (e instanceof
|
|
1095
|
+
if (e instanceof ApiError2)
|
|
897
1096
|
throw await reportApiError(e, apiConfig);
|
|
898
1097
|
throw e;
|
|
899
1098
|
}
|
|
1099
|
+
let fullUrl;
|
|
1100
|
+
try {
|
|
1101
|
+
const built = buildUrl({
|
|
1102
|
+
endpoint,
|
|
1103
|
+
params: pathParams,
|
|
1104
|
+
queryParams,
|
|
1105
|
+
baseApiUrls: apiConfig.baseApiUrls,
|
|
1106
|
+
defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
|
|
1107
|
+
url: input.url,
|
|
1108
|
+
doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
|
|
1109
|
+
configError: (msg) => new ConfigError2(msg, ctx())
|
|
1110
|
+
});
|
|
1111
|
+
fullUrl = built.fullUrl;
|
|
1112
|
+
} catch (e) {
|
|
1113
|
+
if (e instanceof ApiError2)
|
|
1114
|
+
throw await reportApiError(e, apiConfig);
|
|
1115
|
+
throw e;
|
|
1116
|
+
}
|
|
1117
|
+
baseContext.url = fullUrl;
|
|
900
1118
|
baseContext.requestHeaders = headers;
|
|
901
1119
|
baseContext.requestBody = body;
|
|
902
|
-
|
|
903
|
-
|
|
1120
|
+
const execute = async () => {
|
|
1121
|
+
if (apiConfig.onRequest) {
|
|
904
1122
|
const result = await apiConfig.onRequest({
|
|
905
1123
|
endpoint: endpointName,
|
|
906
1124
|
method: endpoint.method,
|
|
@@ -913,30 +1131,30 @@ function createApiClientImpl(apiConfig) {
|
|
|
913
1131
|
headers = result.headers;
|
|
914
1132
|
if (result && "body" in result)
|
|
915
1133
|
body = result.body;
|
|
916
|
-
} catch (e) {
|
|
917
|
-
if (e instanceof ApiError)
|
|
918
|
-
throw await reportApiError(e, apiConfig);
|
|
919
|
-
throw e;
|
|
920
1134
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
|
|
931
|
-
userSignal: input.signal
|
|
932
|
-
};
|
|
933
|
-
try {
|
|
1135
|
+
const config = {
|
|
1136
|
+
method: endpoint.method,
|
|
1137
|
+
fullUrl,
|
|
1138
|
+
body,
|
|
1139
|
+
headers,
|
|
1140
|
+
timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
|
|
1141
|
+
fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
|
|
1142
|
+
userSignal: input.signal
|
|
1143
|
+
};
|
|
934
1144
|
const result = await makeRequestWithRetry({
|
|
935
1145
|
config,
|
|
936
1146
|
maxRetries,
|
|
937
|
-
retryDelay: apiConfig.retryDelayMs ??
|
|
938
|
-
|
|
939
|
-
|
|
1147
|
+
retryDelay: apiConfig.retryDelayMs ?? ((attempt) => exponentialBackoff2(attempt + 1, {
|
|
1148
|
+
baseMs: 300,
|
|
1149
|
+
maxMs: apiConfig.maxRetryDelayMs
|
|
1150
|
+
})),
|
|
1151
|
+
maxRetryDelayMs: apiConfig.maxRetryDelayMs,
|
|
1152
|
+
validateFn: async (response, attempt) => {
|
|
1153
|
+
const validated = await validateAndTransformResponse(endpoint, response, (over) => ctx({ attempt, ...over }));
|
|
1154
|
+
await runCheckResponse(validated.data, response.status, attempt);
|
|
1155
|
+
return validated;
|
|
1156
|
+
},
|
|
1157
|
+
shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry2,
|
|
940
1158
|
logger: apiConfig.logger,
|
|
941
1159
|
ctx,
|
|
942
1160
|
endpointName,
|
|
@@ -946,22 +1164,61 @@ function createApiClientImpl(apiConfig) {
|
|
|
946
1164
|
onResponse: apiConfig.onResponse,
|
|
947
1165
|
onRetry: apiConfig.onRetry
|
|
948
1166
|
});
|
|
949
|
-
const responseTimeMs = elapsed();
|
|
950
|
-
const responseSizeMb = calculateSizeInMb(result.validatedData);
|
|
951
|
-
const requestBodySizeMb = calculateSizeInMb(config.body);
|
|
952
1167
|
return {
|
|
953
|
-
data: result.validatedData,
|
|
954
|
-
requestBodySizeMb,
|
|
955
|
-
responseSizeMb,
|
|
956
|
-
responseTimeMs,
|
|
1168
|
+
data: result.validatedData.data,
|
|
1169
|
+
requestBodySizeMb: calculateSizeInMb(config.body),
|
|
1170
|
+
responseSizeMb: result.validatedData.responseSizeBytes / (1024 * 1024),
|
|
1171
|
+
responseTimeMs: elapsed(),
|
|
957
1172
|
httpStatus: result.httpStatus,
|
|
958
1173
|
retryCount: result.retryCount
|
|
959
1174
|
};
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1175
|
+
};
|
|
1176
|
+
const runCheckResponse = async (data, httpStatus, attempt) => {
|
|
1177
|
+
if (!apiConfig.checkResponse)
|
|
1178
|
+
return;
|
|
1179
|
+
try {
|
|
1180
|
+
await apiConfig.checkResponse(data, {
|
|
1181
|
+
endpoint: endpointName,
|
|
1182
|
+
method: endpoint.method,
|
|
1183
|
+
url: fullUrl,
|
|
1184
|
+
httpStatus,
|
|
1185
|
+
attempt,
|
|
1186
|
+
meta: input.meta
|
|
1187
|
+
});
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
if (error instanceof ApiError2)
|
|
1190
|
+
throw error;
|
|
1191
|
+
throw new ApiResponseError2(error instanceof Error ? error.message : String(error), data, ctx({ attempt }), error);
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
const run = async () => {
|
|
1195
|
+
try {
|
|
1196
|
+
return await execute();
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (error instanceof ApiError2) {
|
|
1199
|
+
throw await reportApiError(error, apiConfig);
|
|
1200
|
+
}
|
|
1201
|
+
throw error;
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const middleware = apiConfig.use ?? [];
|
|
1205
|
+
if (middleware.length === 0)
|
|
1206
|
+
return run();
|
|
1207
|
+
const middlewareContext = {
|
|
1208
|
+
client: apiConfig.name,
|
|
1209
|
+
endpoint: String(endpointKey),
|
|
1210
|
+
method: endpoint.method,
|
|
1211
|
+
url: fullUrl,
|
|
1212
|
+
headers,
|
|
1213
|
+
meta: input.meta
|
|
1214
|
+
};
|
|
1215
|
+
const dispatch = (index) => {
|
|
1216
|
+
const current = middleware[index];
|
|
1217
|
+
if (!current)
|
|
1218
|
+
return run();
|
|
1219
|
+
return current(middlewareContext, () => dispatch(index + 1));
|
|
1220
|
+
};
|
|
1221
|
+
return dispatch(0);
|
|
965
1222
|
};
|
|
966
1223
|
async function invokeAction(name, rawInput, options = {}) {
|
|
967
1224
|
const action = actions[name];
|
|
@@ -984,9 +1241,17 @@ function createApiClientImpl(apiConfig) {
|
|
|
984
1241
|
...over
|
|
985
1242
|
});
|
|
986
1243
|
apiConfig.logger?.debug?.("Action start", { action: actionName });
|
|
1244
|
+
let parsedInput = inputForHandler;
|
|
1245
|
+
if (action.input) {
|
|
1246
|
+
const parsed = await action.input.safeParseAsync(rawInput);
|
|
1247
|
+
if (!parsed.success) {
|
|
1248
|
+
throw await reportApiError(new RequestValidationError2(parsed.error, "input", rawInput, ctx()), apiConfig);
|
|
1249
|
+
}
|
|
1250
|
+
parsedInput = parsed.data;
|
|
1251
|
+
}
|
|
987
1252
|
try {
|
|
988
1253
|
const result = await action.handler({
|
|
989
|
-
input:
|
|
1254
|
+
input: parsedInput,
|
|
990
1255
|
api: proxy,
|
|
991
1256
|
signal: options.signal,
|
|
992
1257
|
meta: options.meta,
|
|
@@ -999,33 +1264,44 @@ function createApiClientImpl(apiConfig) {
|
|
|
999
1264
|
});
|
|
1000
1265
|
return result;
|
|
1001
1266
|
} catch (error) {
|
|
1002
|
-
if (error instanceof
|
|
1267
|
+
if (error instanceof ApiError2) {
|
|
1003
1268
|
throw await reportApiError(error, apiConfig);
|
|
1004
1269
|
}
|
|
1005
|
-
const wrapped = new
|
|
1270
|
+
const wrapped = new ActionError2(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
|
|
1006
1271
|
throw await reportApiError(wrapped, apiConfig);
|
|
1007
1272
|
}
|
|
1008
1273
|
}
|
|
1274
|
+
async function dispatchAction(name, first, second) {
|
|
1275
|
+
const action = actions[name];
|
|
1276
|
+
const { input, options } = resolveActionInvocation(action, name, first, second);
|
|
1277
|
+
return invokeAction(name, input, options);
|
|
1278
|
+
}
|
|
1009
1279
|
const request = async (key, input, options) => {
|
|
1010
1280
|
if (key in actions) {
|
|
1011
|
-
|
|
1012
|
-
if (action.noRuntimeInput) {
|
|
1013
|
-
if (input === undefined || input === null) {
|
|
1014
|
-
return invokeAction(key, undefined, options ?? {});
|
|
1015
|
-
}
|
|
1016
|
-
if (options !== undefined) {
|
|
1017
|
-
return invokeAction(key, undefined, options);
|
|
1018
|
-
}
|
|
1019
|
-
if (isActionInvokeOptionsOnly(input)) {
|
|
1020
|
-
return invokeAction(key, undefined, input);
|
|
1021
|
-
}
|
|
1022
|
-
return invokeAction(key, undefined, {});
|
|
1023
|
-
}
|
|
1024
|
-
return invokeAction(key, input, options);
|
|
1281
|
+
return dispatchAction(key, input, options);
|
|
1025
1282
|
}
|
|
1026
1283
|
return invokeEndpoint(key, input ?? {});
|
|
1027
1284
|
};
|
|
1028
|
-
const
|
|
1285
|
+
const safe = new Proxy({}, {
|
|
1286
|
+
get(_target, prop) {
|
|
1287
|
+
if (typeof prop !== "string")
|
|
1288
|
+
return;
|
|
1289
|
+
if (!(prop in apiConfig.endpoints) && !(prop in actions)) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
return async (...args) => {
|
|
1293
|
+
try {
|
|
1294
|
+
const method = Reflect.get(proxy, prop);
|
|
1295
|
+
return ok2(await method(...args));
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (error instanceof ApiError2)
|
|
1298
|
+
return err2(error);
|
|
1299
|
+
throw error;
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
const client = { request, cache, safe };
|
|
1029
1305
|
const proxy = new Proxy(client, {
|
|
1030
1306
|
get(target, prop, receiver) {
|
|
1031
1307
|
if (prop in target || typeof prop !== "string") {
|
|
@@ -1035,22 +1311,7 @@ function createApiClientImpl(apiConfig) {
|
|
|
1035
1311
|
return (input) => invokeEndpoint(prop, input ?? {});
|
|
1036
1312
|
}
|
|
1037
1313
|
if (prop in actions) {
|
|
1038
|
-
|
|
1039
|
-
if (action.noRuntimeInput) {
|
|
1040
|
-
return (first, second) => {
|
|
1041
|
-
if (first === undefined || first === null) {
|
|
1042
|
-
return invokeAction(prop, undefined, second ?? {});
|
|
1043
|
-
}
|
|
1044
|
-
if (second !== undefined) {
|
|
1045
|
-
return invokeAction(prop, undefined, second);
|
|
1046
|
-
}
|
|
1047
|
-
if (isActionInvokeOptionsOnly(first)) {
|
|
1048
|
-
return invokeAction(prop, undefined, first);
|
|
1049
|
-
}
|
|
1050
|
-
return invokeAction(prop, undefined, {});
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
return (input, opt) => invokeAction(prop, input, opt);
|
|
1314
|
+
return (first, second) => dispatchAction(prop, first, second);
|
|
1054
1315
|
}
|
|
1055
1316
|
return;
|
|
1056
1317
|
}
|
|
@@ -1064,9 +1325,23 @@ function createApiClientImpl(apiConfig) {
|
|
|
1064
1325
|
} else if (rawActions) {
|
|
1065
1326
|
actions = rawActions;
|
|
1066
1327
|
}
|
|
1328
|
+
const reserved = [
|
|
1329
|
+
...Object.keys(apiConfig.endpoints),
|
|
1330
|
+
...Object.keys(actions)
|
|
1331
|
+
].filter((key) => (key in client));
|
|
1332
|
+
if (reserved.length > 0) {
|
|
1333
|
+
throw new ConfigError2(`Endpoint/action name(s) are reserved by the client: ${reserved.join(", ")}`, {
|
|
1334
|
+
endpoint: reserved[0],
|
|
1335
|
+
method: "GET",
|
|
1336
|
+
url: "",
|
|
1337
|
+
attempt: 0,
|
|
1338
|
+
maxRetries: 0,
|
|
1339
|
+
elapsedMs: 0
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1067
1342
|
const collisions = Object.keys(actions).filter((key) => (key in apiConfig.endpoints));
|
|
1068
1343
|
if (collisions.length > 0) {
|
|
1069
|
-
throw new
|
|
1344
|
+
throw new ConfigError2(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
|
|
1070
1345
|
endpoint: collisions[0],
|
|
1071
1346
|
method: "GET",
|
|
1072
1347
|
url: "",
|
|
@@ -1077,160 +1352,88 @@ function createApiClientImpl(apiConfig) {
|
|
|
1077
1352
|
}
|
|
1078
1353
|
return proxy;
|
|
1079
1354
|
}
|
|
1080
|
-
var
|
|
1081
|
-
// src/
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
});
|
|
1095
|
-
async function fetchInfisicalSecrets({
|
|
1096
|
-
keys,
|
|
1097
|
-
projectId,
|
|
1098
|
-
environment,
|
|
1099
|
-
siteUrl,
|
|
1100
|
-
clientId,
|
|
1101
|
-
clientSecret
|
|
1102
|
-
}) {
|
|
1103
|
-
const infisical = createInfisicalApiClient(siteUrl);
|
|
1104
|
-
const { data: auth } = await infisical.request("login", {
|
|
1105
|
-
reqBody: {
|
|
1106
|
-
clientId,
|
|
1107
|
-
clientSecret
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
|
-
const { data } = await infisical.request("listSecrets", {
|
|
1111
|
-
reqHeaders: {
|
|
1112
|
-
Authorization: `Bearer ${auth.accessToken}`
|
|
1113
|
-
},
|
|
1114
|
-
reqQuery: {
|
|
1115
|
-
environment,
|
|
1116
|
-
projectId,
|
|
1117
|
-
secretPath: "/",
|
|
1118
|
-
viewSecretValue: true,
|
|
1119
|
-
expandSecretReferences: true,
|
|
1120
|
-
includeImports: true
|
|
1121
|
-
}
|
|
1122
|
-
});
|
|
1123
|
-
const wantedKeys = new Set(keys);
|
|
1124
|
-
return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1125
|
-
}
|
|
1126
|
-
async function loadInfisicalSecrets({
|
|
1127
|
-
keys,
|
|
1128
|
-
projectId,
|
|
1129
|
-
environment,
|
|
1130
|
-
siteUrl,
|
|
1131
|
-
clientIdEnvKey,
|
|
1132
|
-
clientSecretEnvKey
|
|
1133
|
-
}) {
|
|
1134
|
-
const clientId = process.env[clientIdEnvKey];
|
|
1135
|
-
const clientSecret = process.env[clientSecretEnvKey];
|
|
1136
|
-
if (!clientId || !clientSecret) {
|
|
1137
|
-
throw new Error("Missing Infisical credentials");
|
|
1138
|
-
}
|
|
1139
|
-
return fetchInfisicalSecrets({
|
|
1140
|
-
keys,
|
|
1141
|
-
projectId,
|
|
1142
|
-
environment,
|
|
1143
|
-
siteUrl,
|
|
1144
|
-
clientId,
|
|
1145
|
-
clientSecret
|
|
1146
|
-
});
|
|
1355
|
+
var createApiClient2 = (apiConfig) => createApiClientImpl(apiConfig);
|
|
1356
|
+
// src/api-wrapper/paginate.ts
|
|
1357
|
+
async function* paginate2(fetchPage, getNext, options = {}) {
|
|
1358
|
+
const { maxPages = Number.POSITIVE_INFINITY, signal } = options;
|
|
1359
|
+
let cursor;
|
|
1360
|
+
for (let count = 0;count < maxPages; count++) {
|
|
1361
|
+
signal?.throwIfAborted();
|
|
1362
|
+
const page = await fetchPage(cursor);
|
|
1363
|
+
yield page;
|
|
1364
|
+
const next = getNext(page);
|
|
1365
|
+
if (next == null)
|
|
1366
|
+
return;
|
|
1367
|
+
cursor = next;
|
|
1368
|
+
}
|
|
1147
1369
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
method:
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
resSchema: infisicalAuthResponseSchema,
|
|
1166
|
-
reqBodyFormat: "json"
|
|
1167
|
-
},
|
|
1168
|
-
listSecrets: {
|
|
1169
|
-
method: "GET",
|
|
1170
|
-
path: "/api/v4/secrets",
|
|
1171
|
-
reqHeadersSchema: z.object({
|
|
1172
|
-
Authorization: z.string()
|
|
1173
|
-
}),
|
|
1174
|
-
reqQuerySchema: z.object({
|
|
1175
|
-
projectId: z.string(),
|
|
1176
|
-
environment: z.string(),
|
|
1177
|
-
secretPath: z.string(),
|
|
1178
|
-
viewSecretValue: z.boolean(),
|
|
1179
|
-
expandSecretReferences: z.boolean(),
|
|
1180
|
-
includeImports: z.boolean()
|
|
1181
|
-
}),
|
|
1182
|
-
resSchema: infisicalSecretsResponseSchema
|
|
1370
|
+
// src/api-wrapper/otel.ts
|
|
1371
|
+
var SPAN_KIND_CLIENT = 2;
|
|
1372
|
+
var SPAN_STATUS_ERROR = 2;
|
|
1373
|
+
function otelMiddleware2(tracer, options = {}) {
|
|
1374
|
+
const { spanName, attributes, includeQuery = true, inject } = options;
|
|
1375
|
+
return (context, next) => {
|
|
1376
|
+
const url = includeQuery ? redactUrl(context.url) : context.url.split(/[?#]/, 1)[0];
|
|
1377
|
+
const name = spanName?.(context) ?? (context.client ? `${context.client}.${context.endpoint}` : context.endpoint);
|
|
1378
|
+
return tracer.startActiveSpan(name, {
|
|
1379
|
+
kind: SPAN_KIND_CLIENT,
|
|
1380
|
+
attributes: {
|
|
1381
|
+
"http.request.method": context.method,
|
|
1382
|
+
"url.full": url,
|
|
1383
|
+
"server.address": hostOf(context.url),
|
|
1384
|
+
"api.client": context.client,
|
|
1385
|
+
"api.endpoint": context.endpoint,
|
|
1386
|
+
...attributes?.(context)
|
|
1183
1387
|
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1388
|
+
}, async (span) => {
|
|
1389
|
+
inject?.(context.headers);
|
|
1390
|
+
try {
|
|
1391
|
+
const result = await next();
|
|
1392
|
+
span.setAttributes({
|
|
1393
|
+
"http.response.status_code": result.httpStatus,
|
|
1394
|
+
"http.request.resend_count": result.retryCount || undefined,
|
|
1395
|
+
"http.response.body.size": Math.round(result.responseSizeMb * 1024 * 1024)
|
|
1396
|
+
});
|
|
1397
|
+
return result;
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
recordFailure(span, error);
|
|
1400
|
+
throw error;
|
|
1401
|
+
} finally {
|
|
1402
|
+
span.end();
|
|
1199
1403
|
}
|
|
1200
|
-
}
|
|
1201
|
-
return out;
|
|
1404
|
+
});
|
|
1202
1405
|
};
|
|
1203
1406
|
}
|
|
1204
|
-
function
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1407
|
+
function recordFailure(span, error) {
|
|
1408
|
+
if (error instanceof ApiError2) {
|
|
1409
|
+
const isHttp = error instanceof HttpResponseError2;
|
|
1410
|
+
span.setAttributes({
|
|
1411
|
+
"error.type": isHttp ? String(error.status) : error.name,
|
|
1412
|
+
"api.error.category": error.category,
|
|
1413
|
+
"http.response.status_code": isHttp ? error.status : undefined,
|
|
1414
|
+
"http.request.resend_count": error.context.attempt || undefined
|
|
1415
|
+
});
|
|
1416
|
+
} else {
|
|
1417
|
+
span.setAttributes({
|
|
1418
|
+
"error.type": error instanceof Error ? error.name : "unknown"
|
|
1419
|
+
});
|
|
1216
1420
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
projectId: config.projectId,
|
|
1223
|
-
environment: ctx.environment,
|
|
1224
|
-
siteUrl: config.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
|
|
1225
|
-
clientId: config.clientId,
|
|
1226
|
-
clientSecret: config.clientSecret
|
|
1421
|
+
span.recordException(error instanceof Error ? error : String(error));
|
|
1422
|
+
span.setStatus({
|
|
1423
|
+
code: SPAN_STATUS_ERROR,
|
|
1424
|
+
message: error instanceof Error ? error.message.split(`
|
|
1425
|
+
`, 1)[0] : String(error)
|
|
1227
1426
|
});
|
|
1228
1427
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1428
|
+
function hostOf(url) {
|
|
1429
|
+
try {
|
|
1430
|
+
return new URL(url).hostname;
|
|
1431
|
+
} catch {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1231
1434
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
};
|
|
1435
|
+
// src/api-wrapper/endpoint.ts
|
|
1436
|
+
function defineEndpoint2(endpoint) {
|
|
1437
|
+
return endpoint;
|
|
1438
|
+
}
|
|
1439
|
+
export { ApiError2, HttpResponseError2, ResponseValidationError2, RequestValidationError2, RequestTimeoutError2, NetworkError2, RequestAbortedError2, ConfigError2, ApiResponseError2, ActionError2, isApiError2, isHttpResponseError2, isResponseValidationError2, isRequestValidationError2, isRequestTimeoutError2, isNetworkError2, isRequestAbortedError2, isConfigError2, isActionError2, isApiResponseError2, isNonRetryableApiError2, defineAction2, describeApiError2, apiErrorLogAttributes2, defaultShouldRetry2, createApiClient2, paginate2, otelMiddleware2, defineEndpoint2 };
|