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.
Files changed (172) hide show
  1. package/README.md +832 -219
  2. package/dist/api-wrapper/builders.d.ts +15 -3
  3. package/dist/api-wrapper/create-client.d.ts +1 -1
  4. package/dist/api-wrapper/describe.d.ts +16 -0
  5. package/dist/api-wrapper/endpoint.d.ts +18 -0
  6. package/dist/api-wrapper/errors.d.ts +40 -2
  7. package/dist/api-wrapper/headers.d.ts +2 -1
  8. package/dist/api-wrapper/index.d.ts +5 -0
  9. package/dist/api-wrapper/otel.d.ts +44 -0
  10. package/dist/api-wrapper/paginate.d.ts +12 -0
  11. package/dist/api-wrapper/redaction.d.ts +2 -0
  12. package/dist/api-wrapper/shared.d.ts +16 -2
  13. package/dist/api-wrapper/testing.d.ts +23 -0
  14. package/dist/api-wrapper/transport.d.ts +12 -10
  15. package/dist/api-wrapper/types.d.ts +104 -18
  16. package/dist/array/index.d.ts +50 -4
  17. package/dist/cache/createCache.d.ts +31 -0
  18. package/dist/cache/index.d.ts +45 -2
  19. package/dist/color/contrast.d.ts +40 -0
  20. package/dist/color/index.d.ts +2 -0
  21. package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
  22. package/dist/cool-console-log/index.d.ts +34 -5
  23. package/dist/crypto/encoding.d.ts +18 -0
  24. package/dist/crypto/hash.d.ts +33 -0
  25. package/dist/crypto/index.d.ts +4 -0
  26. package/dist/crypto/sealer.d.ts +25 -0
  27. package/dist/crypto/webhook.d.ts +20 -0
  28. package/dist/date/index.d.ts +46 -2
  29. package/dist/emitter/index.d.ts +21 -0
  30. package/dist/error/index.d.ts +12 -0
  31. package/dist/format/index.d.ts +102 -0
  32. package/dist/function/index.d.ts +60 -0
  33. package/dist/guard/index.d.ts +20 -0
  34. package/dist/i18n/index.d.ts +37 -0
  35. package/dist/index-0zvtcbzy.js +224 -0
  36. package/dist/index-181e6vfw.js +29 -0
  37. package/dist/index-8r2vdzjg.js +87 -0
  38. package/dist/index-hy7kbb99.js +157 -0
  39. package/dist/{src/secrets/loaders.js → index-j1ybktbr.js} +608 -405
  40. package/dist/index-k9yx6bne.js +89 -0
  41. package/dist/index-kj1phgrx.js +169 -0
  42. package/dist/index-mnkcfyx8.js +78 -0
  43. package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
  44. package/dist/index-rp5pqghz.js +157 -0
  45. package/dist/index-srt06xd2.js +15 -0
  46. package/dist/index-wjyb9bsd.js +263 -0
  47. package/dist/index.d.ts +7 -7
  48. package/dist/jwt/index.d.ts +50 -2
  49. package/dist/math/index.d.ts +2 -2
  50. package/dist/math/linearRegressionTrend.d.ts +14 -1
  51. package/dist/math/scale.d.ts +8 -0
  52. package/dist/object/index.d.ts +19 -0
  53. package/dist/p-queue/index.d.ts +1 -1
  54. package/dist/promise/index.d.ts +97 -2
  55. package/dist/secrets/core.d.ts +47 -0
  56. package/dist/secrets/deadline.d.ts +6 -0
  57. package/dist/secrets/environment.d.ts +9 -0
  58. package/dist/secrets/errors.d.ts +11 -0
  59. package/dist/secrets/index.d.ts +13 -25
  60. package/dist/secrets/infisical-cli.d.ts +6 -0
  61. package/dist/secrets/infisical-config.d.ts +17 -0
  62. package/dist/secrets/infisical-session.d.ts +4 -0
  63. package/dist/secrets/infisical.d.ts +8 -13
  64. package/dist/secrets/loaders.d.ts +5 -12
  65. package/dist/secrets/vite.d.ts +7 -3
  66. package/dist/secrets/worker-loader.d.ts +9 -0
  67. package/dist/secrets/worker.d.ts +3 -0
  68. package/dist/sitemap/index.d.ts +48 -6
  69. package/dist/src/api-wrapper/index.js +60 -1100
  70. package/dist/src/api-wrapper/testing.js +20 -0
  71. package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
  72. package/dist/src/crypto/index.js +195 -0
  73. package/dist/src/format/index.js +211 -0
  74. package/dist/src/i18n/index.js +36 -0
  75. package/dist/src/index.js +863 -2809
  76. package/dist/src/jwt/index.js +138 -92
  77. package/dist/src/p-queue/index.js +9 -437
  78. package/dist/src/secrets/index.js +261 -1377
  79. package/dist/src/secrets/vite.js +6 -17
  80. package/dist/src/secrets/worker.js +12 -0
  81. package/dist/src/sitemap/index.js +183 -511
  82. package/dist/src/storage/index.js +119 -0
  83. package/dist/src/zod/index.js +347 -174
  84. package/dist/state/index.d.ts +21 -0
  85. package/dist/storage/index.d.ts +58 -0
  86. package/dist/string/index.d.ts +75 -2
  87. package/dist/typedswitch/index.d.ts +37 -26
  88. package/dist/types/index.d.ts +11 -0
  89. package/dist/url/index.d.ts +50 -1
  90. package/dist/zod/index.d.ts +32 -3
  91. package/dist/zod/introspect.d.ts +19 -0
  92. package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
  93. package/dist/zod/zodMinMaxFilter.d.ts +22 -3
  94. package/dist/zod/zodToTypeString.d.ts +8 -3
  95. package/package.json +87 -7
  96. package/src/api-wrapper/actions-impl.ts +11 -72
  97. package/src/api-wrapper/builders.ts +84 -52
  98. package/src/api-wrapper/create-client.ts +246 -96
  99. package/src/api-wrapper/describe.ts +157 -0
  100. package/src/api-wrapper/endpoint.ts +26 -0
  101. package/src/api-wrapper/error-reporting.ts +7 -1
  102. package/src/api-wrapper/errors.ts +117 -2
  103. package/src/api-wrapper/headers.ts +19 -1
  104. package/src/api-wrapper/index.ts +5 -0
  105. package/src/api-wrapper/otel.ts +139 -0
  106. package/src/api-wrapper/paginate.ts +28 -0
  107. package/src/api-wrapper/redaction.ts +43 -0
  108. package/src/api-wrapper/shared.ts +21 -2
  109. package/src/api-wrapper/testing.ts +48 -0
  110. package/src/api-wrapper/transport.ts +68 -74
  111. package/src/api-wrapper/types.ts +186 -18
  112. package/src/array/index.ts +195 -42
  113. package/src/cache/createCache.ts +157 -0
  114. package/src/cache/index.ts +235 -83
  115. package/src/color/contrast.ts +163 -0
  116. package/src/color/index.ts +22 -0
  117. package/src/{math → color}/metricsSeriesColors.ts +47 -49
  118. package/src/cool-console-log/index.ts +128 -41
  119. package/src/crypto/encoding.ts +83 -0
  120. package/src/crypto/hash.ts +170 -0
  121. package/src/crypto/index.ts +26 -0
  122. package/src/crypto/sealer.ts +168 -0
  123. package/src/crypto/webhook.ts +86 -0
  124. package/src/date/index.ts +109 -34
  125. package/src/emitter/index.ts +82 -0
  126. package/src/error/index.ts +63 -0
  127. package/src/format/index.ts +389 -0
  128. package/src/function/index.ts +201 -0
  129. package/src/guard/index.ts +80 -0
  130. package/src/i18n/index.ts +125 -0
  131. package/src/index.ts +11 -7
  132. package/src/jwt/index.ts +199 -125
  133. package/src/math/index.ts +5 -10
  134. package/src/math/linearRegressionTrend.ts +33 -8
  135. package/src/math/scale.ts +17 -0
  136. package/src/object/index.ts +99 -0
  137. package/src/p-queue/index.ts +2 -0
  138. package/src/promise/index.ts +304 -9
  139. package/src/secrets/core.ts +348 -0
  140. package/src/secrets/deadline.ts +69 -0
  141. package/src/secrets/environment.ts +69 -0
  142. package/src/secrets/errors.ts +29 -0
  143. package/src/secrets/index.ts +79 -210
  144. package/src/secrets/infisical-cli.ts +99 -0
  145. package/src/secrets/infisical-config.ts +70 -0
  146. package/src/secrets/infisical-session.ts +42 -0
  147. package/src/secrets/infisical.ts +214 -66
  148. package/src/secrets/loaders.ts +24 -48
  149. package/src/secrets/vite.ts +44 -15
  150. package/src/secrets/worker-loader.ts +56 -0
  151. package/src/secrets/worker.ts +4 -0
  152. package/src/sitemap/index.ts +293 -130
  153. package/src/state/index.ts +133 -81
  154. package/src/storage/index.ts +187 -0
  155. package/src/string/index.ts +261 -23
  156. package/src/typedswitch/index.ts +134 -103
  157. package/src/types/index.ts +14 -0
  158. package/src/url/index.ts +146 -1
  159. package/src/zod/index.ts +105 -9
  160. package/src/zod/introspect.ts +103 -0
  161. package/src/zod/jsonToZodSchemaMapper.ts +44 -21
  162. package/src/zod/zodMinMaxFilter.ts +37 -13
  163. package/src/zod/zodToTypeString.ts +184 -181
  164. package/dist/api-wrapper/index.js +0 -250
  165. package/dist/gzip/index.d.ts +0 -2
  166. package/dist/gzip/index.js +0 -16
  167. package/dist/index.js +0 -1823
  168. package/dist/jwt/index.js +0 -19
  169. package/dist/sitemap/index.js +0 -552
  170. package/dist/src/gzip/index.js +0 -16
  171. package/dist/src/secrets/infisical.js +0 -1194
  172. 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 ApiError extends Error {
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 HttpResponseError extends ApiError {
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 ResponseValidationError extends ApiError {
270
+ class ResponseValidationError2 extends ApiError2 {
242
271
  zodError;
243
272
  rawResponse;
244
273
  issues;
245
274
  validationIssues;
246
- constructor(zodError, rawResponse, context) {
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 RequestValidationError extends ApiError {
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 RequestTimeoutError extends ApiError {
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 NetworkError extends ApiError {
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 RequestAbortedError extends ApiError {
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 ConfigError extends ApiError {
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 ActionError extends ApiError {
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(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url, configError) {
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
- const finalUrl2 = replacePathParams(url, params, configError);
347
- const queryString2 = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
348
- return {
349
- finalUrl: finalUrl2,
350
- fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
351
- };
352
- }
353
- const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
354
- if (!defaultBaseUrl) {
355
- throw configError("At least one base API URL is required");
356
- }
357
- const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
358
- const configBaseUrl = baseApiUrls[baseUrlKey];
359
- if (!configBaseUrl) {
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 baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
363
- const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
364
- const finalUrl = replacePathParams(`${baseUrl}${path}`, params, configError);
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 buildQueryParamsRaw(endpoint, query) {
372
- const defaultParams = endpoint.reqDefaultQueryParams ?? {};
373
- const merged = { ...defaultParams, ...query ?? {} };
374
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
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 RequestValidationError(error, "query", query, ctx());
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
- const merged = { ...defaultParams, ...parsedQuery };
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 RequestValidationError(error, "body", body, ctx());
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 RequestValidationError(error, "headers", headers, ctx());
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+)/g, (_, key) => {
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 = new Map;
452
- const isExpired = (entry) => entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
453
- const cache = async (key, fn, options) => {
454
- const existing = store.get(key);
455
- if (existing && !isExpired(existing)) {
456
- if (existing.inflight)
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 (def2) => ({
508
- ...def2,
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: true
572
+ noRuntimeInput: def.input === undefined
516
573
  };
517
574
  }
518
575
  return defineAction;
519
576
  }
520
- var defineAction = makeDefineAction();
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 ResponseValidationError(error, data, ctx());
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 = mergeSignals(timeoutController.signal, config.userSignal);
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 RequestAbortedError(ctx(), error);
847
+ throw new RequestAbortedError2(ctx(), error);
645
848
  }
646
849
  if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
647
- throw new RequestTimeoutError(config.timeoutMs, ctx());
850
+ throw new RequestTimeoutError2(config.timeoutMs, ctx());
648
851
  }
649
- throw new RequestAbortedError(ctx(), error);
852
+ throw new RequestAbortedError2(ctx(), error);
650
853
  }
651
- throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
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 retryAfter = response.headers.get("retry-after");
690
- if (retryAfter) {
691
- const seconds = Number(retryAfter);
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
- if (typeof retryDelay === "function")
701
- return retryDelay(attempt);
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
- if (error instanceof HttpResponseError)
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", config);
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 HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
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 instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError) {
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 ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
1002
+ throw new ResponseValidationError2(makeJsonParseZodError(error, text), text, ctx(), { invalidJson: true });
824
1003
  }
825
1004
  } else {
826
- responseData = await response.text();
1005
+ responseData = text;
827
1006
  }
828
1007
  const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
829
- return validateResponse(endpoint.resSchema, transformedData, ctx);
1008
+ const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
1009
+ return { data, responseSizeBytes };
830
1010
  }
831
- function defaultShouldRetry(ctx) {
1011
+ function defaultShouldRetry2(ctx) {
832
1012
  if (ctx.error)
833
1013
  return shouldAttemptRetryOnError(ctx.error);
834
1014
  return shouldAttemptRetryOnHttp(ctx);
835
1015
  }
836
- function delay(ms) {
837
- return new Promise((resolve) => setTimeout(resolve, ms));
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 ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
1079
+ throw await reportApiError(new ConfigError2(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
874
1080
  }
875
1081
  baseContext.method = endpoint.method;
876
- let finalUrl;
877
- let fullUrl;
878
- try {
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
- headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
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 ApiError)
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
- if (apiConfig.onRequest) {
903
- try {
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
- const config = {
923
- method: endpoint.method,
924
- url: finalUrl,
925
- queryParams,
926
- body,
927
- headers,
928
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
929
- timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
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 ?? 300,
938
- validateFn: (response) => validateAndTransformResponse(endpoint, response, ctx),
939
- shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
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
- } catch (error) {
961
- if (error instanceof ApiError)
962
- throw await reportApiError(error, apiConfig);
963
- throw error;
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: inputForHandler,
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 ApiError) {
1267
+ if (error instanceof ApiError2) {
1003
1268
  throw await reportApiError(error, apiConfig);
1004
1269
  }
1005
- const wrapped = new ActionError(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
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
- const action = actions[key];
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 client = { request, cache };
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
- const action = actions[prop];
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 ConfigError(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
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 createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
1081
- // src/secrets/infisical.ts
1082
- import { z } from "zod";
1083
- var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
1084
- var defaultFetch = globalThis.fetch;
1085
- var infisicalAuthResponseSchema = z.object({
1086
- accessToken: z.string()
1087
- });
1088
- var infisicalSecretSchema = z.object({
1089
- secretKey: z.string(),
1090
- secretValue: z.string()
1091
- });
1092
- var infisicalSecretsResponseSchema = z.object({
1093
- secrets: z.array(infisicalSecretSchema)
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
- function createInfisicalApiClient(siteUrl) {
1149
- return createApiClient({
1150
- name: "infisical",
1151
- baseApiUrls: {
1152
- infisical: siteUrl
1153
- },
1154
- defaultBaseApiUrl: "infisical",
1155
- fetch: getFetch(),
1156
- retries: 1,
1157
- endpoints: {
1158
- login: {
1159
- method: "POST",
1160
- path: "/api/v1/auth/universal-auth/login",
1161
- reqBodySchema: z.object({
1162
- clientId: z.string(),
1163
- clientSecret: z.string()
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
- function getFetch() {
1188
- return globalThis.fetch ?? defaultFetch;
1189
- }
1190
-
1191
- // src/secrets/loaders.ts
1192
- function cloudflareWorkerEnvLoader(workerEnv) {
1193
- return async (ctx) => {
1194
- const out = {};
1195
- for (const key of ctx.keys) {
1196
- const str = workerBindingToString(workerEnv[key]);
1197
- if (str !== undefined) {
1198
- out[key] = str;
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 workerBindingToString(value) {
1205
- if (value === undefined || value === null) {
1206
- return;
1207
- }
1208
- if (typeof value === "string") {
1209
- return value === "" ? undefined : value;
1210
- }
1211
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1212
- return String(value);
1213
- }
1214
- if (typeof value === "object") {
1215
- return JSON.stringify(value);
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
- return String(value);
1218
- }
1219
- function infisicalLoader(config) {
1220
- return async (ctx) => fetchInfisicalSecrets({
1221
- keys: ctx.keys,
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
- async function processEnvLoader(_ctx) {
1230
- return {};
1428
+ function hostOf(url) {
1429
+ try {
1430
+ return new URL(url).hostname;
1431
+ } catch {
1432
+ return;
1433
+ }
1231
1434
  }
1232
- export {
1233
- processEnvLoader,
1234
- infisicalLoader,
1235
- cloudflareWorkerEnvLoader
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 };