hanzio 1.2.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +786 -239
- package/dist/api-wrapper/builders.d.ts +4 -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 +39 -2
- 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 +4 -5
- package/dist/api-wrapper/types.d.ts +103 -18
- package/dist/array/index.d.ts +50 -4
- package/dist/cache/createCache.d.ts +31 -0
- package/dist/cache/index.d.ts +19 -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-8r2vdzjg.js +87 -0
- package/dist/index-hy7kbb99.js +157 -0
- package/dist/index-hyt6c56h.js +290 -0
- package/dist/{src/secrets/infisical.js → index-j1ybktbr.js} +500 -538
- package/dist/index-k9yx6bne.js +89 -0
- package/dist/index-kj1phgrx.js +169 -0
- package/dist/index-ktynw3vs.js +37 -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-w882zry3.js +29 -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 +7 -0
- package/dist/secrets/index.d.ts +3 -1
- package/dist/secrets/public-config.d.ts +7 -0
- package/dist/secrets/vite.d.ts +1 -0
- package/dist/secrets/worker.d.ts +1 -0
- package/dist/sitemap/index.d.ts +48 -6
- package/dist/src/api-wrapper/index.js +60 -1130
- 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 +739 -3145
- package/dist/src/jwt/index.js +138 -92
- package/dist/src/p-queue/index.js +9 -437
- package/dist/src/secrets/index.js +50 -1428
- package/dist/src/secrets/vite.js +10 -39
- package/dist/src/secrets/worker.js +14 -273
- package/dist/src/sitemap/index.js +183 -511
- package/dist/src/storage/index.js +119 -0
- package/dist/src/zod/index.js +340 -167
- 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 +31 -24
- 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 +78 -7
- package/src/api-wrapper/actions-impl.ts +11 -72
- package/src/api-wrapper/builders.ts +32 -25
- package/src/api-wrapper/create-client.ts +171 -41
- 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 +107 -2
- package/src/api-wrapper/headers.ts +11 -6
- 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 +42 -0
- package/src/api-wrapper/shared.ts +21 -2
- package/src/api-wrapper/testing.ts +48 -0
- package/src/api-wrapper/transport.ts +41 -43
- package/src/api-wrapper/types.ts +185 -18
- package/src/array/index.ts +195 -42
- package/src/cache/createCache.ts +157 -0
- package/src/cache/index.ts +132 -147
- 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 +72 -16
- package/src/secrets/index.ts +16 -4
- package/src/secrets/public-config.ts +47 -0
- package/src/secrets/vite.ts +2 -0
- package/src/secrets/worker.ts +2 -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 +58 -38
- 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/loaders.js +0 -1529
- package/dist/zod/index.js +0 -210
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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";
|
|
3
15
|
// src/api-wrapper/headers.ts
|
|
4
16
|
var CANONICAL_HEADER_NAMES = {
|
|
5
17
|
"content-type": "Content-Type",
|
|
@@ -47,7 +59,12 @@ function buildFetchBody(body, headers, configError) {
|
|
|
47
59
|
return body;
|
|
48
60
|
if (hasJsonContentType(headers))
|
|
49
61
|
return JSON.stringify(body);
|
|
50
|
-
|
|
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) {
|
|
51
68
|
throw configError("Request body is a plain object but Content-Type is not application/json");
|
|
52
69
|
}
|
|
53
70
|
return body;
|
|
@@ -197,9 +214,12 @@ function tryParseJson(body) {
|
|
|
197
214
|
// src/api-wrapper/errors.ts
|
|
198
215
|
var MAX_BODY_PREVIEW = 500;
|
|
199
216
|
|
|
200
|
-
class
|
|
217
|
+
class ApiError2 extends Error {
|
|
201
218
|
context;
|
|
202
219
|
cause;
|
|
220
|
+
get category() {
|
|
221
|
+
return "unknown";
|
|
222
|
+
}
|
|
203
223
|
constructor(message, context, options) {
|
|
204
224
|
super(message);
|
|
205
225
|
this.name = "ApiError";
|
|
@@ -209,13 +229,14 @@ class ApiError extends Error {
|
|
|
209
229
|
toJSON() {
|
|
210
230
|
return {
|
|
211
231
|
name: this.name,
|
|
232
|
+
category: this.category,
|
|
212
233
|
message: this.message,
|
|
213
234
|
context: this.context
|
|
214
235
|
};
|
|
215
236
|
}
|
|
216
237
|
}
|
|
217
238
|
|
|
218
|
-
class
|
|
239
|
+
class HttpResponseError2 extends ApiError2 {
|
|
219
240
|
status;
|
|
220
241
|
body;
|
|
221
242
|
bodyJson;
|
|
@@ -231,6 +252,9 @@ class HttpResponseError extends ApiError {
|
|
|
231
252
|
this.requestId = getHeader(responseHeaders, "x-request-id") ?? getHeader(responseHeaders, "x-correlation-id");
|
|
232
253
|
this.bodyJson = tryParseJson(body);
|
|
233
254
|
}
|
|
255
|
+
get category() {
|
|
256
|
+
return this.status >= 500 ? "server_error" : "client_error";
|
|
257
|
+
}
|
|
234
258
|
toJSON() {
|
|
235
259
|
return {
|
|
236
260
|
...super.toJSON(),
|
|
@@ -243,25 +267,31 @@ class HttpResponseError extends ApiError {
|
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
|
-
class
|
|
270
|
+
class ResponseValidationError2 extends ApiError2 {
|
|
247
271
|
zodError;
|
|
248
272
|
rawResponse;
|
|
249
273
|
issues;
|
|
250
274
|
validationIssues;
|
|
251
|
-
|
|
275
|
+
invalidJson;
|
|
276
|
+
constructor(zodError, rawResponse, context, options = {}) {
|
|
252
277
|
const issues = formatZodIssues(zodError, rawResponse);
|
|
253
278
|
const summary = renderIssueSummary(issues);
|
|
254
279
|
super(`Response validation failed (${context.method} ${context.url}):
|
|
255
280
|
${summary}`, context, { cause: zodError });
|
|
256
281
|
this.name = "ResponseValidationError";
|
|
282
|
+
this.invalidJson = options.invalidJson ?? false;
|
|
257
283
|
this.zodError = zodError;
|
|
258
284
|
this.rawResponse = rawResponse;
|
|
259
285
|
this.issues = issues;
|
|
260
286
|
this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
261
287
|
}
|
|
288
|
+
get category() {
|
|
289
|
+
return "output_validation";
|
|
290
|
+
}
|
|
262
291
|
toJSON() {
|
|
263
292
|
return {
|
|
264
293
|
...super.toJSON(),
|
|
294
|
+
invalidJson: this.invalidJson,
|
|
265
295
|
issues: this.issues,
|
|
266
296
|
validationIssues: this.validationIssues,
|
|
267
297
|
rawResponsePreview: previewValue(this.rawResponse, 1000)
|
|
@@ -269,7 +299,7 @@ ${summary}`, context, { cause: zodError });
|
|
|
269
299
|
}
|
|
270
300
|
}
|
|
271
301
|
|
|
272
|
-
class
|
|
302
|
+
class RequestValidationError2 extends ApiError2 {
|
|
273
303
|
zodError;
|
|
274
304
|
target;
|
|
275
305
|
rawInput;
|
|
@@ -287,6 +317,9 @@ ${summary}`, context, { cause: zodError });
|
|
|
287
317
|
this.issues = issues;
|
|
288
318
|
this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
289
319
|
}
|
|
320
|
+
get category() {
|
|
321
|
+
return "input_validation";
|
|
322
|
+
}
|
|
290
323
|
toJSON() {
|
|
291
324
|
return {
|
|
292
325
|
...super.toJSON(),
|
|
@@ -297,63 +330,92 @@ ${summary}`, context, { cause: zodError });
|
|
|
297
330
|
}
|
|
298
331
|
}
|
|
299
332
|
|
|
300
|
-
class
|
|
333
|
+
class RequestTimeoutError2 extends ApiError2 {
|
|
301
334
|
timeoutMs;
|
|
302
335
|
constructor(timeoutMs, context) {
|
|
303
336
|
super(`Request timed out after ${timeoutMs}ms: ${context.method} ${context.url}`, context);
|
|
304
337
|
this.name = "RequestTimeoutError";
|
|
305
338
|
this.timeoutMs = timeoutMs;
|
|
306
339
|
}
|
|
340
|
+
get category() {
|
|
341
|
+
return "timeout";
|
|
342
|
+
}
|
|
307
343
|
}
|
|
308
344
|
|
|
309
|
-
class
|
|
345
|
+
class NetworkError2 extends ApiError2 {
|
|
310
346
|
constructor(message, context, cause) {
|
|
311
347
|
super(message, context, { cause });
|
|
312
348
|
this.name = "NetworkError";
|
|
313
349
|
}
|
|
350
|
+
get category() {
|
|
351
|
+
return "network";
|
|
352
|
+
}
|
|
314
353
|
}
|
|
315
354
|
|
|
316
|
-
class
|
|
355
|
+
class RequestAbortedError2 extends ApiError2 {
|
|
317
356
|
constructor(context, cause) {
|
|
318
357
|
super(`Request aborted: ${context.method} ${context.url}`, context, {
|
|
319
358
|
cause
|
|
320
359
|
});
|
|
321
360
|
this.name = "RequestAbortedError";
|
|
322
361
|
}
|
|
362
|
+
get category() {
|
|
363
|
+
return "aborted";
|
|
364
|
+
}
|
|
323
365
|
}
|
|
324
366
|
|
|
325
|
-
class
|
|
367
|
+
class ConfigError2 extends ApiError2 {
|
|
326
368
|
constructor(message, context) {
|
|
327
369
|
super(message, context);
|
|
328
370
|
this.name = "ConfigError";
|
|
329
371
|
}
|
|
372
|
+
get category() {
|
|
373
|
+
return "config";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
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
|
+
}
|
|
330
393
|
}
|
|
331
394
|
|
|
332
|
-
class
|
|
395
|
+
class ActionError2 extends ApiError2 {
|
|
333
396
|
constructor(message, context, cause) {
|
|
334
397
|
super(message, context, { cause });
|
|
335
398
|
this.name = "ActionError";
|
|
336
399
|
}
|
|
400
|
+
get category() {
|
|
401
|
+
return "action";
|
|
402
|
+
}
|
|
337
403
|
}
|
|
338
|
-
var
|
|
339
|
-
var
|
|
340
|
-
var
|
|
341
|
-
var
|
|
342
|
-
var
|
|
343
|
-
var
|
|
344
|
-
var
|
|
345
|
-
var
|
|
346
|
-
var
|
|
347
|
-
|
|
348
|
-
|
|
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;
|
|
349
416
|
}
|
|
350
417
|
// src/api-wrapper/builders.ts
|
|
351
418
|
import { ZodError as ZodError2 } from "zod";
|
|
352
|
-
function formatQueryString(queryParams, doNotEncode) {
|
|
353
|
-
if (Object.keys(queryParams).length === 0)
|
|
354
|
-
return "";
|
|
355
|
-
return doNotEncode ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(queryParams).toString();
|
|
356
|
-
}
|
|
357
419
|
function buildUrl(options) {
|
|
358
420
|
const {
|
|
359
421
|
endpoint,
|
|
@@ -367,7 +429,7 @@ function buildUrl(options) {
|
|
|
367
429
|
} = options;
|
|
368
430
|
let finalUrl;
|
|
369
431
|
if (url) {
|
|
370
|
-
finalUrl =
|
|
432
|
+
finalUrl = replaceUrlPathParams(url, params, configError);
|
|
371
433
|
} else {
|
|
372
434
|
const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
|
|
373
435
|
if (!defaultBaseUrl) {
|
|
@@ -380,9 +442,11 @@ function buildUrl(options) {
|
|
|
380
442
|
}
|
|
381
443
|
const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
|
|
382
444
|
const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
|
|
383
|
-
finalUrl =
|
|
445
|
+
finalUrl = joinUrlPath(baseUrl, replacePathParams(path, params, configError));
|
|
384
446
|
}
|
|
385
|
-
const queryString =
|
|
447
|
+
const queryString = buildQueryString2(queryParams, {
|
|
448
|
+
encode: !doNotEncodeQueryParams
|
|
449
|
+
});
|
|
386
450
|
return {
|
|
387
451
|
finalUrl,
|
|
388
452
|
fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
|
|
@@ -395,7 +459,7 @@ function buildPathParams(endpoint, params, ctx) {
|
|
|
395
459
|
return endpoint.reqParamsSchema.parse(params);
|
|
396
460
|
} catch (error) {
|
|
397
461
|
if (error instanceof ZodError2) {
|
|
398
|
-
throw new
|
|
462
|
+
throw new RequestValidationError2(error, "params", params, ctx());
|
|
399
463
|
}
|
|
400
464
|
throw error;
|
|
401
465
|
}
|
|
@@ -408,15 +472,14 @@ function buildQueryParams(endpoint, query, ctx) {
|
|
|
408
472
|
parsedQuery = endpoint.reqQuerySchema.parse(query);
|
|
409
473
|
} catch (error) {
|
|
410
474
|
if (error instanceof ZodError2) {
|
|
411
|
-
throw new
|
|
475
|
+
throw new RequestValidationError2(error, "query", query, ctx());
|
|
412
476
|
}
|
|
413
477
|
throw error;
|
|
414
478
|
}
|
|
415
479
|
} else {
|
|
416
480
|
parsedQuery = query ?? {};
|
|
417
481
|
}
|
|
418
|
-
|
|
419
|
-
return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
|
|
482
|
+
return { ...defaultParams, ...parsedQuery };
|
|
420
483
|
}
|
|
421
484
|
function buildRequestBody(endpoint, body, ctx) {
|
|
422
485
|
if (!endpoint.reqBodySchema)
|
|
@@ -426,7 +489,7 @@ function buildRequestBody(endpoint, body, ctx) {
|
|
|
426
489
|
parsedBody = endpoint.reqBodySchema.parse(body);
|
|
427
490
|
} catch (error) {
|
|
428
491
|
if (error instanceof ZodError2) {
|
|
429
|
-
throw new
|
|
492
|
+
throw new RequestValidationError2(error, "body", body, ctx());
|
|
430
493
|
}
|
|
431
494
|
throw error;
|
|
432
495
|
}
|
|
@@ -449,7 +512,7 @@ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
|
|
|
449
512
|
parsedHeaders = endpoint.reqHeadersSchema.parse(headers);
|
|
450
513
|
} catch (error) {
|
|
451
514
|
if (error instanceof ZodError2) {
|
|
452
|
-
throw new
|
|
515
|
+
throw new RequestValidationError2(error, "headers", headers, ctx());
|
|
453
516
|
}
|
|
454
517
|
throw error;
|
|
455
518
|
}
|
|
@@ -463,8 +526,20 @@ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
|
|
|
463
526
|
...parsedHeaders
|
|
464
527
|
});
|
|
465
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
|
+
}
|
|
466
541
|
function replacePathParams(path, params, configError) {
|
|
467
|
-
return path.replace(/:(\w
|
|
542
|
+
return path.replace(/:([A-Za-z_]\w*)/g, (_, key) => {
|
|
468
543
|
const value = params?.[key];
|
|
469
544
|
if (value === undefined) {
|
|
470
545
|
throw configError(`Missing required path parameter: ${key}`);
|
|
@@ -475,57 +550,12 @@ function replacePathParams(path, params, configError) {
|
|
|
475
550
|
|
|
476
551
|
// src/api-wrapper/actions-impl.ts
|
|
477
552
|
function createActionCache() {
|
|
478
|
-
const store =
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return existing.inflight;
|
|
485
|
-
if (existing.hasValue)
|
|
486
|
-
return existing.value;
|
|
487
|
-
}
|
|
488
|
-
const promise = (async () => fn())();
|
|
489
|
-
const entry = {
|
|
490
|
-
hasValue: false,
|
|
491
|
-
inflight: promise,
|
|
492
|
-
expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
493
|
-
};
|
|
494
|
-
store.set(key, entry);
|
|
495
|
-
try {
|
|
496
|
-
const value = await promise;
|
|
497
|
-
entry.value = value;
|
|
498
|
-
entry.hasValue = true;
|
|
499
|
-
entry.inflight = undefined;
|
|
500
|
-
return value;
|
|
501
|
-
} catch (error) {
|
|
502
|
-
store.delete(key);
|
|
503
|
-
throw error;
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
cache.get = (key) => {
|
|
507
|
-
const entry = store.get(key);
|
|
508
|
-
if (!entry || !entry.hasValue)
|
|
509
|
-
return;
|
|
510
|
-
if (isExpired(entry)) {
|
|
511
|
-
store.delete(key);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
return entry.value;
|
|
515
|
-
};
|
|
516
|
-
cache.set = (key, value, options) => {
|
|
517
|
-
store.set(key, {
|
|
518
|
-
value,
|
|
519
|
-
hasValue: true,
|
|
520
|
-
expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
521
|
-
});
|
|
522
|
-
};
|
|
523
|
-
cache.invalidate = (key) => {
|
|
524
|
-
store.delete(key);
|
|
525
|
-
};
|
|
526
|
-
cache.clear = () => {
|
|
527
|
-
store.clear();
|
|
528
|
-
};
|
|
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();
|
|
529
559
|
return cache;
|
|
530
560
|
}
|
|
531
561
|
function makeDefineAction() {
|
|
@@ -539,12 +569,125 @@ function makeDefineAction() {
|
|
|
539
569
|
const def = args[0];
|
|
540
570
|
return {
|
|
541
571
|
...def,
|
|
542
|
-
noRuntimeInput:
|
|
572
|
+
noRuntimeInput: def.input === undefined
|
|
543
573
|
};
|
|
544
574
|
}
|
|
545
575
|
return defineAction;
|
|
546
576
|
}
|
|
547
|
-
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
|
+
}
|
|
548
691
|
|
|
549
692
|
// src/api-wrapper/redaction.ts
|
|
550
693
|
var SENSITIVE_HEADER_KEYS = new Set([
|
|
@@ -567,6 +710,16 @@ var SENSITIVE_BODY_KEYS = new Set([
|
|
|
567
710
|
"apikey",
|
|
568
711
|
"api_key"
|
|
569
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
|
+
]);
|
|
570
723
|
function redactHeaders(headers) {
|
|
571
724
|
if (!headers)
|
|
572
725
|
return;
|
|
@@ -595,9 +748,29 @@ function redactBody(body) {
|
|
|
595
748
|
}
|
|
596
749
|
return out;
|
|
597
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
|
+
}
|
|
598
770
|
function defaultRedactContext(ctx) {
|
|
599
771
|
return {
|
|
600
772
|
...ctx,
|
|
773
|
+
url: redactUrl(ctx.url),
|
|
601
774
|
requestHeaders: redactHeaders(ctx.requestHeaders),
|
|
602
775
|
requestBody: redactBody(ctx.requestBody),
|
|
603
776
|
meta: ctx.meta
|
|
@@ -610,11 +783,15 @@ function applyErrorContextRedaction(ctx, config) {
|
|
|
610
783
|
return config.redact ? config.redact(base) : base;
|
|
611
784
|
}
|
|
612
785
|
async function reportApiError(err, config) {
|
|
786
|
+
const rawUrl = err.context.url;
|
|
613
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
|
+
}
|
|
614
791
|
try {
|
|
615
792
|
await config.onError?.(err);
|
|
616
793
|
} catch {}
|
|
617
|
-
config.logger?.error?.(err);
|
|
794
|
+
config.logger?.error?.(describeApiError2(err), apiErrorLogAttributes2(err));
|
|
618
795
|
return err;
|
|
619
796
|
}
|
|
620
797
|
|
|
@@ -648,7 +825,7 @@ async function validateResponse(schema, data, ctx) {
|
|
|
648
825
|
return await schema.parseAsync(data);
|
|
649
826
|
} catch (error) {
|
|
650
827
|
if (error instanceof ZodError3) {
|
|
651
|
-
throw new
|
|
828
|
+
throw new ResponseValidationError2(error, data, ctx());
|
|
652
829
|
}
|
|
653
830
|
throw error;
|
|
654
831
|
}
|
|
@@ -656,27 +833,27 @@ async function validateResponse(schema, data, ctx) {
|
|
|
656
833
|
async function httpRequest(config, ctx) {
|
|
657
834
|
const timeoutController = new AbortController;
|
|
658
835
|
const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
|
|
659
|
-
const signal =
|
|
836
|
+
const signal = anySignal2(timeoutController.signal, config.userSignal);
|
|
660
837
|
try {
|
|
661
838
|
return await config.fetchFn(config.fullUrl, {
|
|
662
839
|
method: config.method,
|
|
663
840
|
headers: config.headers,
|
|
664
|
-
body: buildFetchBody(config.body, config.headers, (msg) => new
|
|
841
|
+
body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError2(msg, ctx())),
|
|
665
842
|
signal
|
|
666
843
|
});
|
|
667
844
|
} catch (error) {
|
|
668
845
|
if (isDomAbortError(error)) {
|
|
669
846
|
if (config.userSignal?.aborted) {
|
|
670
|
-
throw new
|
|
847
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
671
848
|
}
|
|
672
849
|
if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
|
|
673
|
-
throw new
|
|
850
|
+
throw new RequestTimeoutError2(config.timeoutMs, ctx());
|
|
674
851
|
}
|
|
675
|
-
throw new
|
|
852
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
676
853
|
}
|
|
677
|
-
if (error instanceof
|
|
854
|
+
if (error instanceof ConfigError2)
|
|
678
855
|
throw error;
|
|
679
|
-
throw new
|
|
856
|
+
throw new NetworkError2(error instanceof Error ? error.message : String(error), ctx(), error);
|
|
680
857
|
} finally {
|
|
681
858
|
if (timeout)
|
|
682
859
|
clearTimeout(timeout);
|
|
@@ -685,24 +862,6 @@ async function httpRequest(config, ctx) {
|
|
|
685
862
|
function isDomAbortError(error) {
|
|
686
863
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
687
864
|
}
|
|
688
|
-
function mergeSignals(a, b) {
|
|
689
|
-
if (!b)
|
|
690
|
-
return a;
|
|
691
|
-
const anyFn = AbortSignal.any;
|
|
692
|
-
if (typeof anyFn === "function")
|
|
693
|
-
return anyFn([a, b]);
|
|
694
|
-
const controller = new AbortController;
|
|
695
|
-
const onAbort = () => controller.abort();
|
|
696
|
-
if (a.aborted)
|
|
697
|
-
controller.abort();
|
|
698
|
-
else
|
|
699
|
-
a.addEventListener("abort", onAbort, { once: true });
|
|
700
|
-
if (b.aborted)
|
|
701
|
-
controller.abort();
|
|
702
|
-
else
|
|
703
|
-
b.addEventListener("abort", onAbort, { once: true });
|
|
704
|
-
return controller.signal;
|
|
705
|
-
}
|
|
706
865
|
async function readErrorResponse(response) {
|
|
707
866
|
const body = await response.text();
|
|
708
867
|
return {
|
|
@@ -715,23 +874,15 @@ function getRetryDelay(params) {
|
|
|
715
874
|
const maxDelay = params.maxRetryDelayMs ?? 30000;
|
|
716
875
|
const { retryDelay, attempt, response } = params;
|
|
717
876
|
if (response && (response.status === 429 || response.status === 503)) {
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
720
|
-
|
|
721
|
-
if (Number.isFinite(seconds)) {
|
|
722
|
-
return Math.min(Math.max(0, seconds * 1000), maxDelay);
|
|
723
|
-
}
|
|
724
|
-
const dateMs = Date.parse(retryAfter);
|
|
725
|
-
if (!Number.isNaN(dateMs)) {
|
|
726
|
-
return Math.min(Math.max(0, dateMs - Date.now()), maxDelay);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
877
|
+
const retryAfterMs = parseRetryAfter2(response.headers.get("retry-after"));
|
|
878
|
+
if (retryAfterMs !== undefined)
|
|
879
|
+
return Math.min(retryAfterMs, maxDelay);
|
|
729
880
|
}
|
|
730
881
|
const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
|
|
731
882
|
return Math.min(delayMs, maxDelay);
|
|
732
883
|
}
|
|
733
884
|
function shouldAttemptRetryOnError(error) {
|
|
734
|
-
return !
|
|
885
|
+
return !isNonRetryableApiError2(error);
|
|
735
886
|
}
|
|
736
887
|
function shouldAttemptRetryOnHttp(retryCtx) {
|
|
737
888
|
const { error, response } = retryCtx;
|
|
@@ -760,7 +911,12 @@ async function makeRequestWithRetry(args) {
|
|
|
760
911
|
while (true) {
|
|
761
912
|
let response;
|
|
762
913
|
try {
|
|
763
|
-
logger?.debug?.("API request",
|
|
914
|
+
logger?.debug?.("API request", {
|
|
915
|
+
endpoint: endpointName,
|
|
916
|
+
method,
|
|
917
|
+
url: redactUrl(fullUrl),
|
|
918
|
+
attempt: retryCount
|
|
919
|
+
});
|
|
764
920
|
response = await httpRequest(config, (over) => ctx({ attempt: retryCount, ...over }));
|
|
765
921
|
await onResponse?.({
|
|
766
922
|
endpoint: endpointName,
|
|
@@ -791,12 +947,12 @@ async function makeRequestWithRetry(args) {
|
|
|
791
947
|
delayMs,
|
|
792
948
|
nextAttempt: retryCount + 1
|
|
793
949
|
});
|
|
950
|
+
await sleepBeforeRetry(delayMs, config.userSignal, () => ctx({ attempt: retryCount }));
|
|
794
951
|
retryCount++;
|
|
795
|
-
await delay(delayMs);
|
|
796
952
|
continue;
|
|
797
953
|
}
|
|
798
954
|
const { body, headers: responseHeaders } = await readErrorResponse(response);
|
|
799
|
-
throw new
|
|
955
|
+
throw new HttpResponseError2(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
|
|
800
956
|
}
|
|
801
957
|
return {
|
|
802
958
|
validatedData: await validateFn(response, retryCount),
|
|
@@ -804,7 +960,7 @@ async function makeRequestWithRetry(args) {
|
|
|
804
960
|
httpStatus: response.status
|
|
805
961
|
};
|
|
806
962
|
} catch (error) {
|
|
807
|
-
if (
|
|
963
|
+
if (isNonRetryableApiError2(error) && !(error instanceof ApiResponseError2)) {
|
|
808
964
|
throw error;
|
|
809
965
|
}
|
|
810
966
|
const retryCtx = {
|
|
@@ -829,8 +985,8 @@ async function makeRequestWithRetry(args) {
|
|
|
829
985
|
delayMs,
|
|
830
986
|
nextAttempt: retryCount + 1
|
|
831
987
|
});
|
|
988
|
+
await sleepBeforeRetry(delayMs, config.userSignal, () => ctx({ attempt: retryCount }));
|
|
832
989
|
retryCount++;
|
|
833
|
-
await delay(delayMs);
|
|
834
990
|
}
|
|
835
991
|
}
|
|
836
992
|
}
|
|
@@ -843,7 +999,7 @@ async function validateAndTransformResponse(endpoint, response, ctx) {
|
|
|
843
999
|
try {
|
|
844
1000
|
responseData = text === "" ? undefined : JSON.parse(text);
|
|
845
1001
|
} catch (error) {
|
|
846
|
-
throw new
|
|
1002
|
+
throw new ResponseValidationError2(makeJsonParseZodError(error, text), text, ctx(), { invalidJson: true });
|
|
847
1003
|
}
|
|
848
1004
|
} else {
|
|
849
1005
|
responseData = text;
|
|
@@ -852,13 +1008,17 @@ async function validateAndTransformResponse(endpoint, response, ctx) {
|
|
|
852
1008
|
const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
|
|
853
1009
|
return { data, responseSizeBytes };
|
|
854
1010
|
}
|
|
855
|
-
function
|
|
1011
|
+
function defaultShouldRetry2(ctx) {
|
|
856
1012
|
if (ctx.error)
|
|
857
1013
|
return shouldAttemptRetryOnError(ctx.error);
|
|
858
1014
|
return shouldAttemptRetryOnHttp(ctx);
|
|
859
1015
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
1016
|
+
async function sleepBeforeRetry(ms, signal, ctx) {
|
|
1017
|
+
try {
|
|
1018
|
+
await promiseTimeout2(ms, { signal });
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
throw new RequestAbortedError2(ctx(), error);
|
|
1021
|
+
}
|
|
862
1022
|
}
|
|
863
1023
|
|
|
864
1024
|
// src/api-wrapper/create-client.ts
|
|
@@ -882,7 +1042,7 @@ function resolveActionInvocation(action, actionName, first, second) {
|
|
|
882
1042
|
if (isActionInvokeOptionsOnly(first)) {
|
|
883
1043
|
return { input: undefined, options: first };
|
|
884
1044
|
}
|
|
885
|
-
throw new
|
|
1045
|
+
throw new ConfigError2(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
|
|
886
1046
|
endpoint: actionName,
|
|
887
1047
|
method: "GET",
|
|
888
1048
|
url: "",
|
|
@@ -916,9 +1076,11 @@ function createApiClientImpl(apiConfig) {
|
|
|
916
1076
|
...over
|
|
917
1077
|
});
|
|
918
1078
|
if (!endpoint) {
|
|
919
|
-
throw await reportApiError(new
|
|
1079
|
+
throw await reportApiError(new ConfigError2(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
|
|
920
1080
|
}
|
|
921
1081
|
baseContext.method = endpoint.method;
|
|
1082
|
+
if (typeof endpoint.path === "string")
|
|
1083
|
+
baseContext.url = endpoint.path;
|
|
922
1084
|
let pathParams;
|
|
923
1085
|
let queryParams;
|
|
924
1086
|
let body;
|
|
@@ -927,9 +1089,10 @@ function createApiClientImpl(apiConfig) {
|
|
|
927
1089
|
pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
|
|
928
1090
|
queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
|
|
929
1091
|
body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
|
|
930
|
-
|
|
1092
|
+
const defaultHeaders = typeof apiConfig.defaultHeaders === "function" ? await apiConfig.defaultHeaders() : apiConfig.defaultHeaders;
|
|
1093
|
+
headers = buildHeaders(endpoint, rawInput.reqHeaders, defaultHeaders ?? {}, ctx);
|
|
931
1094
|
} catch (e) {
|
|
932
|
-
if (e instanceof
|
|
1095
|
+
if (e instanceof ApiError2)
|
|
933
1096
|
throw await reportApiError(e, apiConfig);
|
|
934
1097
|
throw e;
|
|
935
1098
|
}
|
|
@@ -943,19 +1106,19 @@ function createApiClientImpl(apiConfig) {
|
|
|
943
1106
|
defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
|
|
944
1107
|
url: input.url,
|
|
945
1108
|
doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
|
|
946
|
-
configError: (msg) => new
|
|
1109
|
+
configError: (msg) => new ConfigError2(msg, ctx())
|
|
947
1110
|
});
|
|
948
1111
|
fullUrl = built.fullUrl;
|
|
949
1112
|
} catch (e) {
|
|
950
|
-
if (e instanceof
|
|
1113
|
+
if (e instanceof ApiError2)
|
|
951
1114
|
throw await reportApiError(e, apiConfig);
|
|
952
1115
|
throw e;
|
|
953
1116
|
}
|
|
954
1117
|
baseContext.url = fullUrl;
|
|
955
1118
|
baseContext.requestHeaders = headers;
|
|
956
1119
|
baseContext.requestBody = body;
|
|
957
|
-
|
|
958
|
-
|
|
1120
|
+
const execute = async () => {
|
|
1121
|
+
if (apiConfig.onRequest) {
|
|
959
1122
|
const result = await apiConfig.onRequest({
|
|
960
1123
|
endpoint: endpointName,
|
|
961
1124
|
method: endpoint.method,
|
|
@@ -968,29 +1131,30 @@ function createApiClientImpl(apiConfig) {
|
|
|
968
1131
|
headers = result.headers;
|
|
969
1132
|
if (result && "body" in result)
|
|
970
1133
|
body = result.body;
|
|
971
|
-
} catch (e) {
|
|
972
|
-
if (e instanceof ApiError)
|
|
973
|
-
throw await reportApiError(e, apiConfig);
|
|
974
|
-
throw e;
|
|
975
1134
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
};
|
|
986
|
-
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
|
+
};
|
|
987
1144
|
const result = await makeRequestWithRetry({
|
|
988
1145
|
config,
|
|
989
1146
|
maxRetries,
|
|
990
|
-
retryDelay: apiConfig.retryDelayMs ??
|
|
1147
|
+
retryDelay: apiConfig.retryDelayMs ?? ((attempt) => exponentialBackoff2(attempt + 1, {
|
|
1148
|
+
baseMs: 300,
|
|
1149
|
+
maxMs: apiConfig.maxRetryDelayMs
|
|
1150
|
+
})),
|
|
991
1151
|
maxRetryDelayMs: apiConfig.maxRetryDelayMs,
|
|
992
|
-
validateFn: (response, attempt) =>
|
|
993
|
-
|
|
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,
|
|
994
1158
|
logger: apiConfig.logger,
|
|
995
1159
|
ctx,
|
|
996
1160
|
endpointName,
|
|
@@ -1000,22 +1164,61 @@ function createApiClientImpl(apiConfig) {
|
|
|
1000
1164
|
onResponse: apiConfig.onResponse,
|
|
1001
1165
|
onRetry: apiConfig.onRetry
|
|
1002
1166
|
});
|
|
1003
|
-
const responseTimeMs = elapsed();
|
|
1004
|
-
const responseSizeMb = result.validatedData.responseSizeBytes / (1024 * 1024);
|
|
1005
|
-
const requestBodySizeMb = calculateSizeInMb(config.body);
|
|
1006
1167
|
return {
|
|
1007
1168
|
data: result.validatedData.data,
|
|
1008
|
-
requestBodySizeMb,
|
|
1009
|
-
responseSizeMb,
|
|
1010
|
-
responseTimeMs,
|
|
1169
|
+
requestBodySizeMb: calculateSizeInMb(config.body),
|
|
1170
|
+
responseSizeMb: result.validatedData.responseSizeBytes / (1024 * 1024),
|
|
1171
|
+
responseTimeMs: elapsed(),
|
|
1011
1172
|
httpStatus: result.httpStatus,
|
|
1012
1173
|
retryCount: result.retryCount
|
|
1013
1174
|
};
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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);
|
|
1019
1222
|
};
|
|
1020
1223
|
async function invokeAction(name, rawInput, options = {}) {
|
|
1021
1224
|
const action = actions[name];
|
|
@@ -1038,9 +1241,17 @@ function createApiClientImpl(apiConfig) {
|
|
|
1038
1241
|
...over
|
|
1039
1242
|
});
|
|
1040
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
|
+
}
|
|
1041
1252
|
try {
|
|
1042
1253
|
const result = await action.handler({
|
|
1043
|
-
input:
|
|
1254
|
+
input: parsedInput,
|
|
1044
1255
|
api: proxy,
|
|
1045
1256
|
signal: options.signal,
|
|
1046
1257
|
meta: options.meta,
|
|
@@ -1053,10 +1264,10 @@ function createApiClientImpl(apiConfig) {
|
|
|
1053
1264
|
});
|
|
1054
1265
|
return result;
|
|
1055
1266
|
} catch (error) {
|
|
1056
|
-
if (error instanceof
|
|
1267
|
+
if (error instanceof ApiError2) {
|
|
1057
1268
|
throw await reportApiError(error, apiConfig);
|
|
1058
1269
|
}
|
|
1059
|
-
const wrapped = new
|
|
1270
|
+
const wrapped = new ActionError2(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
|
|
1060
1271
|
throw await reportApiError(wrapped, apiConfig);
|
|
1061
1272
|
}
|
|
1062
1273
|
}
|
|
@@ -1071,7 +1282,26 @@ function createApiClientImpl(apiConfig) {
|
|
|
1071
1282
|
}
|
|
1072
1283
|
return invokeEndpoint(key, input ?? {});
|
|
1073
1284
|
};
|
|
1074
|
-
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 };
|
|
1075
1305
|
const proxy = new Proxy(client, {
|
|
1076
1306
|
get(target, prop, receiver) {
|
|
1077
1307
|
if (prop in target || typeof prop !== "string") {
|
|
@@ -1095,9 +1325,23 @@ function createApiClientImpl(apiConfig) {
|
|
|
1095
1325
|
} else if (rawActions) {
|
|
1096
1326
|
actions = rawActions;
|
|
1097
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
|
+
}
|
|
1098
1342
|
const collisions = Object.keys(actions).filter((key) => (key in apiConfig.endpoints));
|
|
1099
1343
|
if (collisions.length > 0) {
|
|
1100
|
-
throw new
|
|
1344
|
+
throw new ConfigError2(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
|
|
1101
1345
|
endpoint: collisions[0],
|
|
1102
1346
|
method: "GET",
|
|
1103
1347
|
url: "",
|
|
@@ -1108,370 +1352,88 @@ function createApiClientImpl(apiConfig) {
|
|
|
1108
1352
|
}
|
|
1109
1353
|
return proxy;
|
|
1110
1354
|
}
|
|
1111
|
-
var
|
|
1112
|
-
// src/
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
+
}
|
|
1369
|
+
}
|
|
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)
|
|
1387
|
+
}
|
|
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();
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
};
|
|
1124
1406
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (options.signal?.aborted) {
|
|
1135
|
-
cancel();
|
|
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
|
+
});
|
|
1136
1416
|
} else {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const timer = setTimeout(() => controller.abort(new SecretLoadError("TIMEOUT", `Secret loading timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
1140
|
-
let rejectOnAbort = () => {};
|
|
1141
|
-
try {
|
|
1142
|
-
const aborted = new Promise((_, reject) => {
|
|
1143
|
-
rejectOnAbort = () => reject(controller.signal.reason);
|
|
1144
|
-
controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
1417
|
+
span.setAttributes({
|
|
1418
|
+
"error.type": error instanceof Error ? error.name : "unknown"
|
|
1145
1419
|
});
|
|
1146
|
-
if (controller.signal.aborted) {
|
|
1147
|
-
throw controller.signal.reason;
|
|
1148
|
-
}
|
|
1149
|
-
return await Promise.race([operation(controller.signal), aborted]);
|
|
1150
|
-
} finally {
|
|
1151
|
-
clearTimeout(timer);
|
|
1152
|
-
options.signal?.removeEventListener("abort", cancel);
|
|
1153
|
-
controller.signal.removeEventListener("abort", rejectOnAbort);
|
|
1154
1420
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}
|
|
1161
|
-
function resolveSecretEnvironment(option, env) {
|
|
1162
|
-
const explicit = typeof option === "function" ? option() : option;
|
|
1163
|
-
if (typeof explicit === "object" && explicit !== null) {
|
|
1164
|
-
if (typeof explicit.slug !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(explicit.slug)) {
|
|
1165
|
-
throw new SecretLoadError("CONFIGURATION", "Custom secret environment requires a valid slug.");
|
|
1166
|
-
}
|
|
1167
|
-
return explicit.slug;
|
|
1168
|
-
}
|
|
1169
|
-
const selected = explicit ?? env?.SECRETS_ENV;
|
|
1170
|
-
if (selected !== undefined) {
|
|
1171
|
-
if (selected === "production" || selected === "prod") {
|
|
1172
|
-
return "prod";
|
|
1173
|
-
}
|
|
1174
|
-
if (selected === "development" || selected === "dev") {
|
|
1175
|
-
return "dev";
|
|
1176
|
-
}
|
|
1177
|
-
if (selected === "staging") {
|
|
1178
|
-
return "staging";
|
|
1179
|
-
}
|
|
1180
|
-
throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
|
|
1181
|
-
}
|
|
1182
|
-
if (env?.NODE_ENV === "production" || env?.NODE_ENV === "prod") {
|
|
1183
|
-
return "prod";
|
|
1184
|
-
}
|
|
1185
|
-
if (env?.NODE_ENV === "staging") {
|
|
1186
|
-
return "staging";
|
|
1187
|
-
}
|
|
1188
|
-
return "dev";
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// src/secrets/infisical-cli.ts
|
|
1192
|
-
async function readInfisicalCliToken(options) {
|
|
1193
|
-
const { execFile } = await import("node:child_process").catch(() => {
|
|
1194
|
-
throw new SecretLoadError("CLI", "Infisical CLI fallback requires a runtime with subprocess support. Provide Infisical client credentials or a custom secret loader.");
|
|
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)
|
|
1195
1426
|
});
|
|
1196
|
-
options.signal.throwIfAborted();
|
|
1197
|
-
const env = {
|
|
1198
|
-
...getProcessEnvironment(),
|
|
1199
|
-
INFISICAL_DISABLE_UPDATE_CHECK: "true"
|
|
1200
|
-
};
|
|
1201
|
-
delete env.INFISICAL_TOKEN;
|
|
1202
|
-
delete env.INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN;
|
|
1203
|
-
delete env.TOKEN;
|
|
1204
|
-
try {
|
|
1205
|
-
const stdout = await new Promise((resolve, reject) => {
|
|
1206
|
-
const child = execFile("infisical", [
|
|
1207
|
-
"user",
|
|
1208
|
-
"get",
|
|
1209
|
-
"token",
|
|
1210
|
-
"--plain",
|
|
1211
|
-
`--domain=${options.siteUrl}`,
|
|
1212
|
-
"--silent"
|
|
1213
|
-
], {
|
|
1214
|
-
env,
|
|
1215
|
-
encoding: "utf8",
|
|
1216
|
-
shell: false,
|
|
1217
|
-
timeout: options.timeoutMs ?? 30000,
|
|
1218
|
-
signal: options.signal,
|
|
1219
|
-
killSignal: "SIGKILL",
|
|
1220
|
-
maxBuffer: 10485760,
|
|
1221
|
-
windowsHide: true
|
|
1222
|
-
}, (error, output) => {
|
|
1223
|
-
if (error) {
|
|
1224
|
-
reject(error);
|
|
1225
|
-
} else {
|
|
1226
|
-
resolve(output);
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1229
|
-
child.stdin?.end();
|
|
1230
|
-
});
|
|
1231
|
-
return stdout.trim();
|
|
1232
|
-
} catch (error) {
|
|
1233
|
-
if (options.signal.aborted) {
|
|
1234
|
-
throw options.signal.reason;
|
|
1235
|
-
}
|
|
1236
|
-
const failure = error;
|
|
1237
|
-
if (failure?.code === "ENOENT") {
|
|
1238
|
-
throw new SecretLoadError("CLI", "Infisical CLI not found. Install infisical and ensure it is on PATH, or provide Infisical client credentials.");
|
|
1239
|
-
}
|
|
1240
|
-
if (failure?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
1241
|
-
throw new SecretLoadError("CLI", "Infisical CLI output exceeded the 10 MiB limit.");
|
|
1242
|
-
}
|
|
1243
|
-
if (failure?.killed) {
|
|
1244
|
-
throw new SecretLoadError("TIMEOUT", `Infisical CLI timed out after ${(options.timeoutMs ?? 30000) / 1000} seconds or was terminated.`);
|
|
1245
|
-
}
|
|
1246
|
-
throw new SecretLoadError("CLI", `Infisical CLI session token failed. Run infisical login --domain=${options.siteUrl} and verify access to the configured organization, project, and environment.`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// src/secrets/infisical-config.ts
|
|
1251
|
-
function validateInfisicalScope(options) {
|
|
1252
|
-
validateInfisicalOrganizationId(options.organizationId);
|
|
1253
|
-
let url;
|
|
1254
|
-
try {
|
|
1255
|
-
url = new URL(options.siteUrl);
|
|
1256
|
-
} catch {
|
|
1257
|
-
throw new SecretLoadError("CONFIGURATION", "Invalid Infisical siteUrl.");
|
|
1258
|
-
}
|
|
1259
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
1260
|
-
throw new SecretLoadError("CONFIGURATION", "Infisical siteUrl must be an HTTP(S) URL without credentials, query, or fragment.");
|
|
1261
|
-
}
|
|
1262
|
-
if (!options.projectId.trim() || !options.environment.trim() || !(options.secretPath ?? "/").startsWith("/")) {
|
|
1263
|
-
throw new SecretLoadError("CONFIGURATION", "Infisical requires a projectId, environment, and absolute secretPath.");
|
|
1264
|
-
}
|
|
1265
1427
|
}
|
|
1266
|
-
function
|
|
1267
|
-
if (typeof organizationId !== "string" || !organizationId.trim() || /\s/.test(organizationId)) {
|
|
1268
|
-
throw new SecretLoadError("CONFIGURATION", "Infisical organizationId is required and must be a nonempty ID without whitespace.");
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
// src/secrets/infisical-session.ts
|
|
1273
|
-
import { z } from "zod";
|
|
1274
|
-
var sessionClaimsSchema = z.object({
|
|
1275
|
-
organizationId: z.string().optional(),
|
|
1276
|
-
subOrganizationId: z.string().optional(),
|
|
1277
|
-
exp: z.number().finite().optional()
|
|
1278
|
-
});
|
|
1279
|
-
function parseInfisicalSessionToken(token) {
|
|
1280
|
-
let claims;
|
|
1428
|
+
function hostOf(url) {
|
|
1281
1429
|
try {
|
|
1282
|
-
|
|
1283
|
-
throw new Error("Invalid token format");
|
|
1284
|
-
}
|
|
1285
|
-
const payload = token.split(".")[1];
|
|
1286
|
-
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1287
|
-
claims = sessionClaimsSchema.parse(JSON.parse(json));
|
|
1430
|
+
return new URL(url).hostname;
|
|
1288
1431
|
} catch {
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
if (claims.exp !== undefined && claims.exp <= Date.now() / 1000) {
|
|
1292
|
-
throw new SecretLoadError("AUTHENTICATION", "Infisical user session has expired. Run infisical login for the configured domain.");
|
|
1293
|
-
}
|
|
1294
|
-
return { organizationId: claims.subOrganizationId ?? claims.organizationId };
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// src/secrets/infisical.ts
|
|
1298
|
-
var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
|
|
1299
|
-
var defaultFetch = globalThis.fetch;
|
|
1300
|
-
var infisicalAuthResponseSchema = z2.object({ accessToken: z2.string() });
|
|
1301
|
-
var infisicalSecretSchema = z2.object({
|
|
1302
|
-
secretKey: z2.string(),
|
|
1303
|
-
secretValue: z2.string()
|
|
1304
|
-
});
|
|
1305
|
-
var infisicalSecretsResponseSchema = z2.object({
|
|
1306
|
-
secrets: z2.array(infisicalSecretSchema)
|
|
1307
|
-
});
|
|
1308
|
-
async function fetchInfisicalSecrets(options) {
|
|
1309
|
-
validateInfisicalScope(options);
|
|
1310
|
-
return withSecretDeadline(options, async (signal) => {
|
|
1311
|
-
let operation = "login";
|
|
1312
|
-
try {
|
|
1313
|
-
const infisical = createInfisicalApiClient(options.siteUrl);
|
|
1314
|
-
const { data: auth } = await infisical.request("login", {
|
|
1315
|
-
signal,
|
|
1316
|
-
reqBody: {
|
|
1317
|
-
clientId: options.clientId,
|
|
1318
|
-
clientSecret: options.clientSecret
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1321
|
-
signal.throwIfAborted();
|
|
1322
|
-
operation = "listSecrets";
|
|
1323
|
-
return await listInfisicalSecrets(infisical, auth.accessToken, {
|
|
1324
|
-
...options,
|
|
1325
|
-
signal
|
|
1326
|
-
});
|
|
1327
|
-
} catch (error) {
|
|
1328
|
-
throwInfisicalHttpError(error, operation, signal);
|
|
1329
|
-
}
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1332
|
-
async function loadInfisicalSecrets(options) {
|
|
1333
|
-
validateInfisicalScope(options);
|
|
1334
|
-
const env = getProcessEnvironment();
|
|
1335
|
-
const clientId = env?.[options.clientIdEnvKey];
|
|
1336
|
-
const clientSecret = env?.[options.clientSecretEnvKey];
|
|
1337
|
-
const mode = options.auth ?? "auto";
|
|
1338
|
-
if (!["auto", "http", "cli"].includes(mode)) {
|
|
1339
|
-
throw new SecretLoadError("CONFIGURATION", "Invalid Infisical auth mode.");
|
|
1340
|
-
}
|
|
1341
|
-
const useHttp = mode === "http" || mode === "auto" && Boolean(clientId && clientSecret);
|
|
1342
|
-
if (useHttp) {
|
|
1343
|
-
if (!clientId || !clientSecret) {
|
|
1344
|
-
throw new SecretLoadError("AUTHENTICATION", "Infisical HTTP authentication requires both client credentials.");
|
|
1345
|
-
}
|
|
1346
|
-
options.reportSource?.("Infisical HTTP");
|
|
1347
|
-
return fetchInfisicalSecrets({ ...options, clientId, clientSecret });
|
|
1348
|
-
}
|
|
1349
|
-
options.reportSource?.(`Infisical CLI session (organization=${options.organizationId} project=${options.projectId} environment=${options.environment})`);
|
|
1350
|
-
return withSecretDeadline(options, (signal) => fetchInfisicalSessionSecrets({ ...options, signal }));
|
|
1351
|
-
}
|
|
1352
|
-
async function fetchInfisicalSessionSecrets(options) {
|
|
1353
|
-
const { signal } = options;
|
|
1354
|
-
let token = await readInfisicalCliToken(options);
|
|
1355
|
-
signal.throwIfAborted();
|
|
1356
|
-
const session = parseInfisicalSessionToken(token);
|
|
1357
|
-
const infisical = createInfisicalApiClient(options.siteUrl);
|
|
1358
|
-
let operation = "selectOrganization";
|
|
1359
|
-
try {
|
|
1360
|
-
if (session.organizationId !== options.organizationId) {
|
|
1361
|
-
const { data } = await infisical.request("selectOrganization", {
|
|
1362
|
-
signal,
|
|
1363
|
-
reqHeaders: {
|
|
1364
|
-
Authorization: `Bearer ${token}`,
|
|
1365
|
-
"User-Agent": "hanzio/secrets"
|
|
1366
|
-
},
|
|
1367
|
-
reqBody: { organizationId: options.organizationId }
|
|
1368
|
-
});
|
|
1369
|
-
signal.throwIfAborted();
|
|
1370
|
-
if (data.isMfaEnabled) {
|
|
1371
|
-
throw new SecretLoadError("AUTHENTICATION", "Infisical requires MFA for this organization. Complete infisical login for the target organization and configured domain before loading secrets.", { operation });
|
|
1372
|
-
}
|
|
1373
|
-
const scopedSession = parseInfisicalSessionToken(data.token);
|
|
1374
|
-
if (scopedSession.organizationId !== options.organizationId) {
|
|
1375
|
-
throw new SecretLoadError("AUTHENTICATION", "Infisical returned a session for a different organization.", { operation });
|
|
1376
|
-
}
|
|
1377
|
-
token = data.token;
|
|
1378
|
-
}
|
|
1379
|
-
signal.throwIfAborted();
|
|
1380
|
-
operation = "listSecrets";
|
|
1381
|
-
return await listInfisicalSecrets(infisical, token, options);
|
|
1382
|
-
} catch (error) {
|
|
1383
|
-
throwInfisicalHttpError(error, operation, signal);
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
async function listInfisicalSecrets(infisical, token, options) {
|
|
1387
|
-
const { data } = await infisical.request("listSecrets", {
|
|
1388
|
-
signal: options.signal,
|
|
1389
|
-
reqHeaders: { Authorization: `Bearer ${token}` },
|
|
1390
|
-
reqQuery: {
|
|
1391
|
-
environment: options.environment,
|
|
1392
|
-
projectId: options.projectId,
|
|
1393
|
-
secretPath: options.secretPath ?? "/",
|
|
1394
|
-
viewSecretValue: true,
|
|
1395
|
-
expandSecretReferences: true,
|
|
1396
|
-
includeImports: true
|
|
1397
|
-
}
|
|
1398
|
-
});
|
|
1399
|
-
const wantedKeys = new Set(options.keys);
|
|
1400
|
-
return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1401
|
-
}
|
|
1402
|
-
function throwInfisicalHttpError(error, operation, signal) {
|
|
1403
|
-
if (signal.aborted) {
|
|
1404
|
-
throw signal.reason;
|
|
1405
|
-
}
|
|
1406
|
-
if (error instanceof SecretLoadError) {
|
|
1407
|
-
throw error;
|
|
1432
|
+
return;
|
|
1408
1433
|
}
|
|
1409
|
-
const status = error instanceof HttpResponseError ? error.status : undefined;
|
|
1410
|
-
const guidance = operation === "selectOrganization" && (status === 401 || status === 403) ? " Run infisical login for the target organization and configured domain, then verify your access." : "";
|
|
1411
|
-
throw new SecretLoadError("HTTP", `Infisical HTTP ${operation} failed${status ? ` (status ${status})` : ""}.${guidance}`, { operation, status });
|
|
1412
|
-
}
|
|
1413
|
-
function createInfisicalApiClient(siteUrl) {
|
|
1414
|
-
return createApiClient({
|
|
1415
|
-
name: "infisical",
|
|
1416
|
-
baseApiUrls: {
|
|
1417
|
-
infisical: siteUrl
|
|
1418
|
-
},
|
|
1419
|
-
defaultBaseApiUrl: "infisical",
|
|
1420
|
-
fetch: getFetch(),
|
|
1421
|
-
retries: 1,
|
|
1422
|
-
endpoints: {
|
|
1423
|
-
selectOrganization: {
|
|
1424
|
-
method: "POST",
|
|
1425
|
-
path: "/api/v3/auth/select-organization",
|
|
1426
|
-
reqHeadersSchema: z2.object({
|
|
1427
|
-
Authorization: z2.string(),
|
|
1428
|
-
"User-Agent": z2.string()
|
|
1429
|
-
}),
|
|
1430
|
-
reqBodySchema: z2.object({ organizationId: z2.string() }),
|
|
1431
|
-
resSchema: z2.object({
|
|
1432
|
-
token: z2.string().min(1),
|
|
1433
|
-
isMfaEnabled: z2.boolean().optional()
|
|
1434
|
-
}),
|
|
1435
|
-
reqBodyFormat: "json"
|
|
1436
|
-
},
|
|
1437
|
-
login: {
|
|
1438
|
-
method: "POST",
|
|
1439
|
-
path: "/api/v1/auth/universal-auth/login",
|
|
1440
|
-
reqBodySchema: z2.object({
|
|
1441
|
-
clientId: z2.string(),
|
|
1442
|
-
clientSecret: z2.string()
|
|
1443
|
-
}),
|
|
1444
|
-
resSchema: infisicalAuthResponseSchema,
|
|
1445
|
-
reqBodyFormat: "json"
|
|
1446
|
-
},
|
|
1447
|
-
listSecrets: {
|
|
1448
|
-
method: "GET",
|
|
1449
|
-
path: "/api/v4/secrets",
|
|
1450
|
-
reqHeadersSchema: z2.object({
|
|
1451
|
-
Authorization: z2.string()
|
|
1452
|
-
}),
|
|
1453
|
-
reqQuerySchema: z2.object({
|
|
1454
|
-
projectId: z2.string(),
|
|
1455
|
-
environment: z2.string(),
|
|
1456
|
-
secretPath: z2.string(),
|
|
1457
|
-
viewSecretValue: z2.boolean(),
|
|
1458
|
-
expandSecretReferences: z2.boolean(),
|
|
1459
|
-
includeImports: z2.boolean()
|
|
1460
|
-
}),
|
|
1461
|
-
resSchema: infisicalSecretsResponseSchema
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1465
1434
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
return
|
|
1469
|
-
init?.signal?.throwIfAborted();
|
|
1470
|
-
return fetchFn(input, init);
|
|
1471
|
-
};
|
|
1435
|
+
// src/api-wrapper/endpoint.ts
|
|
1436
|
+
function defineEndpoint2(endpoint) {
|
|
1437
|
+
return endpoint;
|
|
1472
1438
|
}
|
|
1473
|
-
export {
|
|
1474
|
-
DEFAULT_INFISICAL_SITE_URL,
|
|
1475
|
-
fetchInfisicalSecrets,
|
|
1476
|
-
loadInfisicalSecrets
|
|
1477
|
-
};
|
|
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 };
|