hanzio 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/api-wrapper/actions-impl.d.ts +4 -0
  2. package/dist/api-wrapper/builders.d.ts +12 -0
  3. package/dist/api-wrapper/create-client.d.ts +6 -0
  4. package/dist/api-wrapper/error-reporting.d.ts +7 -0
  5. package/dist/api-wrapper/errors.d.ts +63 -0
  6. package/dist/api-wrapper/headers.d.ts +9 -0
  7. package/dist/api-wrapper/index.d.ts +5 -84
  8. package/dist/api-wrapper/redaction.d.ts +2 -0
  9. package/dist/api-wrapper/shared.d.ts +16 -0
  10. package/dist/api-wrapper/size.d.ts +1 -0
  11. package/dist/api-wrapper/test-helpers.d.ts +6 -0
  12. package/dist/api-wrapper/transport.d.ts +52 -0
  13. package/dist/api-wrapper/types.d.ts +153 -0
  14. package/dist/api-wrapper/zod-issues.d.ts +17 -0
  15. package/dist/secrets/index.d.ts +10 -8
  16. package/dist/secrets/infisical.d.ts +19 -2
  17. package/dist/secrets/loaders.d.ts +16 -0
  18. package/dist/src/api-wrapper/index.js +1018 -166
  19. package/dist/src/index.js +1155 -232
  20. package/dist/src/secrets/index.js +1138 -230
  21. package/dist/src/secrets/infisical.js +1028 -173
  22. package/dist/src/secrets/loaders.js +1236 -0
  23. package/package.json +2 -2
  24. package/src/api-wrapper/actions-impl.ts +100 -0
  25. package/src/api-wrapper/builders.ts +175 -0
  26. package/src/api-wrapper/create-client.ts +373 -0
  27. package/src/api-wrapper/error-reporting.ts +31 -0
  28. package/src/api-wrapper/errors.ts +221 -0
  29. package/src/api-wrapper/headers.ts +66 -0
  30. package/src/api-wrapper/index.ts +5 -492
  31. package/src/api-wrapper/redaction.ts +61 -0
  32. package/src/api-wrapper/shared.ts +25 -0
  33. package/src/api-wrapper/size.ts +19 -0
  34. package/src/api-wrapper/test-helpers.ts +25 -0
  35. package/src/api-wrapper/transport.ts +354 -0
  36. package/src/api-wrapper/types.ts +258 -0
  37. package/src/api-wrapper/zod-issues.ts +157 -0
  38. package/src/secrets/index.ts +125 -81
  39. package/src/secrets/infisical.ts +50 -14
  40. package/src/secrets/loaders.ts +67 -0
package/dist/src/index.js CHANGED
@@ -198,160 +198,353 @@ function normalizeSingleOrArray(value) {
198
198
  return [...value];
199
199
  return [value];
200
200
  }
201
+ // src/api-wrapper/headers.ts
202
+ var CANONICAL_HEADER_NAMES = {
203
+ "content-type": "Content-Type",
204
+ authorization: "Authorization",
205
+ accept: "Accept",
206
+ "user-agent": "User-Agent",
207
+ "x-request-id": "X-Request-Id",
208
+ "x-correlation-id": "X-Correlation-Id"
209
+ };
210
+ function normalizeHeaders(headers) {
211
+ const result = {};
212
+ const lowerToCanonical = new Map;
213
+ for (const [key, value] of Object.entries(headers)) {
214
+ const lower = key.toLowerCase();
215
+ const canonical = CANONICAL_HEADER_NAMES[lower] ?? key;
216
+ const existing = lowerToCanonical.get(lower);
217
+ if (existing && existing !== canonical)
218
+ delete result[existing];
219
+ lowerToCanonical.set(lower, canonical);
220
+ result[canonical] = value;
221
+ }
222
+ return result;
223
+ }
224
+ function getHeader(headers, name) {
225
+ const lower = name.toLowerCase();
226
+ const canonical = CANONICAL_HEADER_NAMES[lower];
227
+ if (canonical !== undefined) {
228
+ if (headers[canonical] !== undefined)
229
+ return headers[canonical];
230
+ }
231
+ for (const [k, v] of Object.entries(headers)) {
232
+ if (k.toLowerCase() === lower)
233
+ return v;
234
+ }
235
+ return;
236
+ }
237
+ function hasJsonContentType(headers) {
238
+ const ct = getHeader(headers, "content-type") ?? "";
239
+ return ct.includes("application/json");
240
+ }
241
+ function buildFetchBody(body, headers) {
242
+ if (body === undefined || body === null)
243
+ return;
244
+ if (body instanceof FormData || body instanceof Blob)
245
+ return body;
246
+ if (hasJsonContentType(headers))
247
+ return JSON.stringify(body);
248
+ return body;
249
+ }
250
+ function getHeadersAsObject(headers) {
251
+ const result = {};
252
+ headers.forEach((value, key) => {
253
+ result[key] = value;
254
+ });
255
+ return result;
256
+ }
201
257
 
202
- // src/api-wrapper/index.ts
258
+ // src/api-wrapper/zod-issues.ts
203
259
  import { ZodError } from "zod";
260
+ function formatZodIssues(error, rawData) {
261
+ return error.issues.map((issue) => {
262
+ const pathArray = [...issue.path];
263
+ const path = formatIssuePath(pathArray);
264
+ const value = getValueAtPath(rawData, pathArray);
265
+ const valuePreview = value === undefined ? undefined : previewValue(value);
266
+ const raw = issue;
267
+ const expected = typeof raw.expected === "string" ? raw.expected : undefined;
268
+ const received = typeof raw.received === "string" ? raw.received : value !== undefined ? typeofVerbose(value) : undefined;
269
+ const known = new Set([
270
+ "code",
271
+ "path",
272
+ "message",
273
+ "expected",
274
+ "received",
275
+ "fatal"
276
+ ]);
277
+ const extra = {};
278
+ for (const [k, v] of Object.entries(raw)) {
279
+ if (!known.has(k) && v !== undefined)
280
+ extra[k] = v;
281
+ }
282
+ return {
283
+ path,
284
+ pathArray,
285
+ code: issue.code,
286
+ message: issue.message,
287
+ expected,
288
+ received,
289
+ value,
290
+ valuePreview,
291
+ extra: Object.keys(extra).length > 0 ? extra : undefined
292
+ };
293
+ });
294
+ }
295
+ function renderIssueSummary(issues) {
296
+ if (issues.length === 0)
297
+ return "(no issues reported)";
298
+ return issues.map((issue, idx) => {
299
+ const parts = [];
300
+ parts.push(` [${idx + 1}] ${issue.path}: ${issue.message}`);
301
+ if (issue.expected || issue.received) {
302
+ const exp = issue.expected ? `expected ${issue.expected}` : "";
303
+ const rec = issue.received ? `received ${issue.received}` : "";
304
+ parts.push(` (${[exp, rec].filter(Boolean).join(", ")})`);
305
+ }
306
+ if (issue.valuePreview !== undefined) {
307
+ parts.push(` value: ${issue.valuePreview}`);
308
+ }
309
+ if (issue.extra) {
310
+ for (const [k, v] of Object.entries(issue.extra)) {
311
+ parts.push(` ${k}: ${previewValue(v, 120)}`);
312
+ }
313
+ }
314
+ return parts.join(`
315
+ `);
316
+ }).join(`
317
+ `);
318
+ }
319
+ function formatIssuePath(pathArray) {
320
+ if (pathArray.length === 0)
321
+ return "<root>";
322
+ let out = "";
323
+ for (const segment of pathArray) {
324
+ if (typeof segment === "number") {
325
+ out += `[${segment}]`;
326
+ } else if (/^[A-Za-z_$][\w$]*$/.test(segment)) {
327
+ out += out === "" ? segment : `.${segment}`;
328
+ } else {
329
+ out += `[${JSON.stringify(segment)}]`;
330
+ }
331
+ }
332
+ return out;
333
+ }
334
+ function getValueAtPath(data, pathArray) {
335
+ let current = data;
336
+ for (const segment of pathArray) {
337
+ if (current === null || current === undefined)
338
+ return;
339
+ if (typeof current !== "object")
340
+ return;
341
+ current = current[segment];
342
+ }
343
+ return current;
344
+ }
345
+ function typeofVerbose(value) {
346
+ if (value === null)
347
+ return "null";
348
+ if (Array.isArray(value))
349
+ return "array";
350
+ return typeof value;
351
+ }
352
+ function previewValue(value, maxLen = 200) {
353
+ let str;
354
+ try {
355
+ if (value === undefined)
356
+ str = "undefined";
357
+ else if (typeof value === "string")
358
+ str = JSON.stringify(value);
359
+ else
360
+ str = JSON.stringify(value);
361
+ } catch {
362
+ str = String(value);
363
+ }
364
+ if (str === undefined)
365
+ str = String(value);
366
+ if (str.length > maxLen)
367
+ str = `${str.slice(0, maxLen)}…`;
368
+ return str;
369
+ }
370
+ function makeJsonParseZodError(error, text) {
371
+ const message = error instanceof Error ? `Response body was not valid JSON: ${error.message}` : "Response body was not valid JSON";
372
+ const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;
373
+ return new ZodError([
374
+ {
375
+ code: "custom",
376
+ path: [],
377
+ message: `${message} (body preview: ${JSON.stringify(preview)})`,
378
+ input: text
379
+ }
380
+ ]);
381
+ }
382
+ function tryParseJson(body) {
383
+ if (!body)
384
+ return;
385
+ try {
386
+ return JSON.parse(body);
387
+ } catch {
388
+ return;
389
+ }
390
+ }
391
+
392
+ // src/api-wrapper/errors.ts
393
+ var MAX_BODY_PREVIEW = 500;
394
+
395
+ class ApiError extends Error {
396
+ context;
397
+ cause;
398
+ constructor(message, context, options) {
399
+ super(message);
400
+ this.name = "ApiError";
401
+ this.context = context;
402
+ this.cause = options?.cause;
403
+ }
404
+ toJSON() {
405
+ return {
406
+ name: this.name,
407
+ message: this.message,
408
+ context: this.context
409
+ };
410
+ }
411
+ }
412
+
413
+ class HttpResponseError extends ApiError {
414
+ status;
415
+ body;
416
+ bodyJson;
417
+ responseHeaders;
418
+ requestId;
419
+ constructor(status, body, responseHeaders, context) {
420
+ const preview = body.length > MAX_BODY_PREVIEW ? `${body.slice(0, MAX_BODY_PREVIEW)}…` : body;
421
+ super(`HTTP ${status} ${context.method} ${context.url} - ${preview}`, context);
422
+ this.name = "HttpResponseError";
423
+ this.status = status;
424
+ this.body = body;
425
+ this.responseHeaders = responseHeaders;
426
+ this.requestId = getHeader(responseHeaders, "x-request-id") ?? getHeader(responseHeaders, "x-correlation-id");
427
+ this.bodyJson = tryParseJson(body);
428
+ }
429
+ toJSON() {
430
+ return {
431
+ ...super.toJSON(),
432
+ status: this.status,
433
+ body: this.body,
434
+ bodyJson: this.bodyJson,
435
+ requestId: this.requestId,
436
+ responseHeaders: this.responseHeaders
437
+ };
438
+ }
439
+ }
204
440
 
205
- class ResponseValidationError extends Error {
441
+ class ResponseValidationError extends ApiError {
206
442
  zodError;
207
443
  rawResponse;
444
+ issues;
208
445
  validationIssues;
209
- constructor(zodError, rawResponse) {
210
- const issues = zodError.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
211
- super(`Response validation failed: ${issues}`);
446
+ constructor(zodError, rawResponse, context) {
447
+ const issues = formatZodIssues(zodError, rawResponse);
448
+ const summary = renderIssueSummary(issues);
449
+ super(`Response validation failed (${context.method} ${context.url}):
450
+ ${summary}`, context, { cause: zodError });
212
451
  this.name = "ResponseValidationError";
213
452
  this.zodError = zodError;
214
453
  this.rawResponse = rawResponse;
215
- this.validationIssues = issues;
454
+ this.issues = issues;
455
+ this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
456
+ }
457
+ toJSON() {
458
+ return {
459
+ ...super.toJSON(),
460
+ issues: this.issues,
461
+ validationIssues: this.validationIssues,
462
+ rawResponsePreview: previewValue(this.rawResponse, 1000)
463
+ };
216
464
  }
217
465
  }
218
466
 
219
- class HttpResponseError extends Error {
220
- status;
221
- body;
222
- constructor(status, body) {
223
- super(`HTTP error: ${status} - ${body}`);
224
- this.name = "HttpResponseError";
225
- this.status = status;
226
- this.body = body;
467
+ class RequestValidationError extends ApiError {
468
+ zodError;
469
+ target;
470
+ rawInput;
471
+ issues;
472
+ validationIssues;
473
+ constructor(zodError, target, rawInput, context) {
474
+ const issues = formatZodIssues(zodError, rawInput);
475
+ const summary = renderIssueSummary(issues);
476
+ super(`Request ${target} validation failed (${context.method} ${context.url}):
477
+ ${summary}`, context, { cause: zodError });
478
+ this.name = "RequestValidationError";
479
+ this.zodError = zodError;
480
+ this.target = target;
481
+ this.rawInput = rawInput;
482
+ this.issues = issues;
483
+ this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
484
+ }
485
+ toJSON() {
486
+ return {
487
+ ...super.toJSON(),
488
+ target: this.target,
489
+ issues: this.issues,
490
+ validationIssues: this.validationIssues
491
+ };
227
492
  }
228
493
  }
229
- function createApiClient(apiConfig) {
230
- const request = async (endpointKey, input = {}) => {
231
- const endpoint = apiConfig.endpoints[endpointKey];
232
- if (!endpoint) {
233
- throw new Error(`Unknown API endpoint: ${String(endpointKey)}`);
234
- }
235
- const { finalUrl, fullUrl } = buildUrl(endpoint, input.reqParams, input.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url);
236
- const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
237
- const startedAt = Date.now();
238
- let retryCount = 0;
239
- let httpStatus = 0;
240
- apiConfig.onRequestStart?.({ endpoint: endpointName, url: fullUrl });
241
- try {
242
- const config = {
243
- method: endpoint.method,
244
- url: finalUrl,
245
- queryParams: buildQueryParams(endpoint, input.reqQuery),
246
- body: buildRequestBody(endpoint, input.reqBody),
247
- headers: buildHeaders(endpoint, input.reqHeaders, apiConfig.defaultHeaders ?? {}),
248
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
249
- timeoutMs: apiConfig.timeoutMs,
250
- fetchFn: apiConfig.fetch ?? fetch
251
- };
252
- const result = await makeRequestWithRetry(config, apiConfig.retries ?? 3, apiConfig.retryDelayMs ?? 300, (response) => validateAndTransformResponse(endpoint, response), apiConfig.shouldRetry ?? defaultShouldRetry, apiConfig.logger);
253
- retryCount = result.retryCount;
254
- httpStatus = result.httpStatus;
255
- const responseTimeMs = Date.now() - startedAt;
256
- const responseSizeMb = calculateSizeInMb(result.validatedData);
257
- const requestSizeMb = calculateSizeInMb({
258
- method: config.method,
259
- url: fullUrl,
260
- body: config.body,
261
- headers: config.headers
262
- });
263
- apiConfig.onRequestEnd?.({
264
- endpoint: endpointName,
265
- url: fullUrl,
266
- status: httpStatus,
267
- retryCount,
268
- responseTimeMs
269
- });
270
- return {
271
- data: result.validatedData,
272
- requestSizeMb,
273
- responseSizeMb,
274
- requestSizeTotalMb: requestSizeMb + responseSizeMb,
275
- responseTimeMs,
276
- httpStatus,
277
- retryCount
278
- };
279
- } catch (error) {
280
- apiConfig.logger?.error?.(error);
281
- throw error;
282
- }
283
- };
284
- return { request };
494
+
495
+ class RequestTimeoutError extends ApiError {
496
+ timeoutMs;
497
+ constructor(timeoutMs, context) {
498
+ super(`Request timed out after ${timeoutMs}ms: ${context.method} ${context.url}`, context);
499
+ this.name = "RequestTimeoutError";
500
+ this.timeoutMs = timeoutMs;
501
+ }
285
502
  }
286
- var createApiWrapper = createApiClient;
287
- async function validateResponse(schema, data) {
288
- try {
289
- return await schema.parseAsync(data);
290
- } catch (error) {
291
- if (error instanceof ZodError) {
292
- throw new ResponseValidationError(error, data);
293
- }
294
- throw error;
503
+
504
+ class NetworkError extends ApiError {
505
+ constructor(message, context, cause) {
506
+ super(message, context, { cause });
507
+ this.name = "NetworkError";
295
508
  }
296
509
  }
297
- async function httpRequest(config) {
298
- const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
299
- const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
300
- const abortController = new AbortController;
301
- const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => abortController.abort(), config.timeoutMs);
302
- try {
303
- return await config.fetchFn(fullUrl, {
304
- method: config.method,
305
- headers: config.headers,
306
- body: buildFetchBody(config.body, config.headers),
307
- signal: abortController.signal
510
+
511
+ class RequestAbortedError extends ApiError {
512
+ constructor(context, cause) {
513
+ super(`Request aborted: ${context.method} ${context.url}`, context, {
514
+ cause
308
515
  });
309
- } finally {
310
- if (timeout)
311
- clearTimeout(timeout);
516
+ this.name = "RequestAbortedError";
312
517
  }
313
518
  }
314
- async function makeRequestWithRetry(config, maxRetries, retryDelayMs, validateFn, shouldRetry, logger) {
315
- let retryCount = 0;
316
- while (true) {
317
- try {
318
- logger?.debug?.("API request", config);
319
- const response = await httpRequest(config);
320
- if (response.status >= 400) {
321
- if (retryCount < maxRetries && shouldRetry({ response, retryCount, maxRetries })) {
322
- retryCount++;
323
- await delay(retryDelayMs);
324
- continue;
325
- }
326
- throw new HttpResponseError(response.status, await response.text());
327
- }
328
- return {
329
- validatedData: await validateFn(response),
330
- retryCount,
331
- httpStatus: response.status
332
- };
333
- } catch (error) {
334
- if (error instanceof HttpResponseError)
335
- throw error;
336
- if (retryCount >= maxRetries || !shouldRetry({ error, retryCount, maxRetries })) {
337
- throw error;
338
- }
339
- retryCount++;
340
- await delay(retryDelayMs);
341
- }
519
+
520
+ class ConfigError extends ApiError {
521
+ constructor(message, context) {
522
+ super(message, context);
523
+ this.name = "ConfigError";
342
524
  }
343
525
  }
344
- async function validateAndTransformResponse(endpoint, response) {
345
- const responseHeaders = getHeadersAsObject(response.headers);
346
- const contentType = responseHeaders["content-type"] ?? "";
347
- const responseData = contentType.includes("application/json") ? await response.json() : await response.text();
348
- const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
349
- return validateResponse(endpoint.resSchema, transformedData);
350
- }
351
- function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url) {
526
+
527
+ class ActionError extends ApiError {
528
+ constructor(message, context, cause) {
529
+ super(message, context, { cause });
530
+ this.name = "ActionError";
531
+ }
532
+ }
533
+ var isApiError = (e) => e instanceof ApiError;
534
+ var isHttpResponseError = (e) => e instanceof HttpResponseError;
535
+ var isResponseValidationError = (e) => e instanceof ResponseValidationError;
536
+ var isRequestValidationError = (e) => e instanceof RequestValidationError;
537
+ var isRequestTimeoutError = (e) => e instanceof RequestTimeoutError;
538
+ var isNetworkError = (e) => e instanceof NetworkError;
539
+ var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
540
+ var isConfigError = (e) => e instanceof ConfigError;
541
+ var isActionError = (e) => e instanceof ActionError;
542
+ // src/api-wrapper/builders.ts
543
+ import { ZodError as ZodError2 } from "zod";
544
+ function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url, configError) {
352
545
  if (url) {
353
- const finalUrl2 = replacePathParams(url, params);
354
- const queryString2 = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
546
+ const finalUrl2 = replacePathParams(url, params, configError);
547
+ const queryString2 = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
355
548
  return {
356
549
  finalUrl: finalUrl2,
357
550
  fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
@@ -359,32 +552,57 @@ function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, ur
359
552
  }
360
553
  const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
361
554
  if (!defaultBaseUrl) {
362
- throw new Error("At least one base API URL is required");
555
+ throw configError("At least one base API URL is required");
363
556
  }
364
557
  const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
365
558
  const configBaseUrl = baseApiUrls[baseUrlKey];
366
559
  if (!configBaseUrl) {
367
- throw new Error(`Unknown base API URL: ${baseUrlKey}`);
560
+ throw configError(`Unknown base API URL: ${baseUrlKey}`);
368
561
  }
369
562
  const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
370
563
  const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
371
- const finalUrl = replacePathParams(`${baseUrl}${path}`, params);
372
- const queryString = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
564
+ const finalUrl = replacePathParams(`${baseUrl}${path}`, params, configError);
565
+ const queryString = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
373
566
  return {
374
567
  finalUrl,
375
568
  fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
376
569
  };
377
570
  }
378
- function buildQueryParams(endpoint, query) {
571
+ function buildQueryParamsRaw(endpoint, query) {
379
572
  const defaultParams = endpoint.reqDefaultQueryParams ?? {};
380
- const parsedQuery = endpoint.reqQuerySchema ? endpoint.reqQuerySchema.parse(query) : query ?? {};
573
+ const merged = { ...defaultParams, ...query ?? {} };
574
+ return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
575
+ }
576
+ function buildQueryParams(endpoint, query, ctx) {
577
+ const defaultParams = endpoint.reqDefaultQueryParams ?? {};
578
+ let parsedQuery;
579
+ if (endpoint.reqQuerySchema) {
580
+ try {
581
+ parsedQuery = endpoint.reqQuerySchema.parse(query);
582
+ } catch (error) {
583
+ if (error instanceof ZodError2) {
584
+ throw new RequestValidationError(error, "query", query, ctx());
585
+ }
586
+ throw error;
587
+ }
588
+ } else {
589
+ parsedQuery = query ?? {};
590
+ }
381
591
  const merged = { ...defaultParams, ...parsedQuery };
382
592
  return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
383
593
  }
384
- function buildRequestBody(endpoint, body) {
594
+ function buildRequestBody(endpoint, body, ctx) {
385
595
  if (!endpoint.reqBodySchema)
386
596
  return body;
387
- const parsedBody = endpoint.reqBodySchema.parse(body);
597
+ let parsedBody;
598
+ try {
599
+ parsedBody = endpoint.reqBodySchema.parse(body);
600
+ } catch (error) {
601
+ if (error instanceof ZodError2) {
602
+ throw new RequestValidationError(error, "body", body, ctx());
603
+ }
604
+ throw error;
605
+ }
388
606
  if (endpoint.reqBodyFormat !== "form-data") {
389
607
  return parsedBody;
390
608
  }
@@ -396,54 +614,670 @@ function buildRequestBody(endpoint, body) {
396
614
  }
397
615
  return formData;
398
616
  }
399
- function buildHeaders(endpoint, headers, defaultHeaders) {
617
+ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
400
618
  const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
401
- const parsedHeaders = endpoint.reqHeadersSchema ? endpoint.reqHeadersSchema.parse(headers) : headers ?? {};
402
- return {
619
+ let parsedHeaders;
620
+ if (endpoint.reqHeadersSchema) {
621
+ try {
622
+ parsedHeaders = endpoint.reqHeadersSchema.parse(headers);
623
+ } catch (error) {
624
+ if (error instanceof ZodError2) {
625
+ throw new RequestValidationError(error, "headers", headers, ctx());
626
+ }
627
+ throw error;
628
+ }
629
+ } else {
630
+ parsedHeaders = headers ?? {};
631
+ }
632
+ return normalizeHeaders({
403
633
  ...defaultHeaders,
404
634
  ...contentType && { "Content-Type": contentType },
405
635
  ...endpoint.defaultHeaders,
406
636
  ...parsedHeaders
407
- };
408
- }
409
- function buildFetchBody(body, headers) {
410
- if (body === undefined || body === null)
411
- return;
412
- if (body instanceof FormData || body instanceof Blob)
413
- return body;
414
- if (headers["Content-Type"] === "application/json")
415
- return JSON.stringify(body);
416
- return body;
637
+ });
417
638
  }
418
- function replacePathParams(path, params) {
639
+ function replacePathParams(path, params, configError) {
419
640
  return path.replace(/:(\w+)/g, (_, key) => {
420
641
  const value = params?.[key];
421
642
  if (value === undefined) {
422
- throw new Error(`Missing required path parameter: ${key}`);
643
+ throw configError(`Missing required path parameter: ${key}`);
423
644
  }
424
645
  return encodeURIComponent(String(value));
425
646
  });
426
647
  }
427
- function getHeadersAsObject(headers) {
428
- const result = {};
429
- headers.forEach((value, key) => {
430
- result[key] = value;
431
- });
432
- return result;
648
+
649
+ // src/api-wrapper/actions-impl.ts
650
+ function createActionCache() {
651
+ const store = new Map;
652
+ const isExpired = (entry) => entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
653
+ const cache = async (key, fn, options) => {
654
+ const existing = store.get(key);
655
+ if (existing && !isExpired(existing)) {
656
+ if (existing.inflight)
657
+ return existing.inflight;
658
+ if (existing.hasValue)
659
+ return existing.value;
660
+ }
661
+ const promise = (async () => fn())();
662
+ const entry = {
663
+ hasValue: false,
664
+ inflight: promise,
665
+ expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
666
+ };
667
+ store.set(key, entry);
668
+ try {
669
+ const value = await promise;
670
+ entry.value = value;
671
+ entry.hasValue = true;
672
+ entry.inflight = undefined;
673
+ return value;
674
+ } catch (error) {
675
+ store.delete(key);
676
+ throw error;
677
+ }
678
+ };
679
+ cache.get = (key) => {
680
+ const entry = store.get(key);
681
+ if (!entry || !entry.hasValue)
682
+ return;
683
+ if (isExpired(entry)) {
684
+ store.delete(key);
685
+ return;
686
+ }
687
+ return entry.value;
688
+ };
689
+ cache.set = (key, value, options) => {
690
+ store.set(key, {
691
+ value,
692
+ hasValue: true,
693
+ expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
694
+ });
695
+ };
696
+ cache.invalidate = (key) => {
697
+ store.delete(key);
698
+ };
699
+ cache.clear = () => {
700
+ store.clear();
701
+ };
702
+ return cache;
703
+ }
704
+ function makeDefineAction() {
705
+ function defineAction(...args) {
706
+ if (args.length === 0) {
707
+ return (def2) => ({
708
+ ...def2,
709
+ noRuntimeInput: false
710
+ });
711
+ }
712
+ const def = args[0];
713
+ return {
714
+ ...def,
715
+ noRuntimeInput: true
716
+ };
717
+ }
718
+ return defineAction;
719
+ }
720
+ var defineAction = makeDefineAction();
721
+
722
+ // src/api-wrapper/redaction.ts
723
+ var SENSITIVE_HEADER_KEYS = new Set([
724
+ "authorization",
725
+ "cookie",
726
+ "set-cookie",
727
+ "x-api-key",
728
+ "x-auth-token"
729
+ ]);
730
+ var SENSITIVE_BODY_KEYS = new Set([
731
+ "password",
732
+ "token",
733
+ "accesstoken",
734
+ "access_token",
735
+ "refreshtoken",
736
+ "refresh_token",
737
+ "secret",
738
+ "client_secret",
739
+ "apikey",
740
+ "api_key"
741
+ ]);
742
+ function redactHeaders(headers) {
743
+ if (!headers)
744
+ return;
745
+ const out = {};
746
+ for (const [k, v] of Object.entries(headers)) {
747
+ if (SENSITIVE_HEADER_KEYS.has(k.toLowerCase())) {
748
+ out[k] = "[REDACTED]";
749
+ } else {
750
+ out[k] = v;
751
+ }
752
+ }
753
+ return out;
754
+ }
755
+ function redactBody(body) {
756
+ if (body === null || body === undefined)
757
+ return body;
758
+ if (typeof body !== "object" || Array.isArray(body)) {
759
+ return body;
760
+ }
761
+ const rec = body;
762
+ const out = { ...rec };
763
+ for (const key of Object.keys(out)) {
764
+ if (SENSITIVE_BODY_KEYS.has(key.toLowerCase())) {
765
+ out[key] = "[REDACTED]";
766
+ }
767
+ }
768
+ return out;
769
+ }
770
+ function defaultRedactContext(ctx) {
771
+ return {
772
+ ...ctx,
773
+ requestHeaders: redactHeaders(ctx.requestHeaders),
774
+ requestBody: redactBody(ctx.requestBody),
775
+ meta: ctx.meta
776
+ };
777
+ }
778
+
779
+ // src/api-wrapper/error-reporting.ts
780
+ function applyErrorContextRedaction(ctx, config) {
781
+ const base = defaultRedactContext(ctx);
782
+ return config.redact ? config.redact(base) : base;
433
783
  }
784
+ async function reportApiError(err, config) {
785
+ err.context = applyErrorContextRedaction(err.context, config);
786
+ try {
787
+ await config.onError?.(err);
788
+ } catch {}
789
+ config.logger?.error?.(err);
790
+ return err;
791
+ }
792
+
793
+ // src/api-wrapper/size.ts
434
794
  function calculateSizeInMb(data) {
435
- const sizeInBytes = new TextEncoder().encode(JSON.stringify(data)).length;
436
- return sizeInBytes / (1024 * 1024);
795
+ let bytes;
796
+ if (data === undefined || data === null) {
797
+ bytes = 0;
798
+ } else if (data instanceof Blob) {
799
+ bytes = data.size;
800
+ } else if (typeof FormData !== "undefined" && data instanceof FormData) {
801
+ bytes = 0;
802
+ data.forEach((value) => {
803
+ if (value instanceof Blob)
804
+ bytes += value.size;
805
+ else
806
+ bytes += new TextEncoder().encode(String(value)).length;
807
+ });
808
+ } else if (typeof data === "string") {
809
+ bytes = new TextEncoder().encode(data).length;
810
+ } else {
811
+ bytes = new TextEncoder().encode(JSON.stringify(data) ?? "").length;
812
+ }
813
+ return bytes / (1024 * 1024);
814
+ }
815
+
816
+ // src/api-wrapper/transport.ts
817
+ import { ZodError as ZodError3 } from "zod";
818
+ async function validateResponse(schema, data, ctx) {
819
+ try {
820
+ return await schema.parseAsync(data);
821
+ } catch (error) {
822
+ if (error instanceof ZodError3) {
823
+ throw new ResponseValidationError(error, data, ctx());
824
+ }
825
+ throw error;
826
+ }
827
+ }
828
+ async function httpRequest(config, ctx) {
829
+ const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
830
+ const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
831
+ const timeoutController = new AbortController;
832
+ const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
833
+ const signal = mergeSignals(timeoutController.signal, config.userSignal);
834
+ try {
835
+ return await config.fetchFn(fullUrl, {
836
+ method: config.method,
837
+ headers: config.headers,
838
+ body: buildFetchBody(config.body, config.headers),
839
+ signal
840
+ });
841
+ } catch (error) {
842
+ if (isDomAbortError(error)) {
843
+ if (config.userSignal?.aborted) {
844
+ throw new RequestAbortedError(ctx(), error);
845
+ }
846
+ if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
847
+ throw new RequestTimeoutError(config.timeoutMs, ctx());
848
+ }
849
+ throw new RequestAbortedError(ctx(), error);
850
+ }
851
+ throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
852
+ } finally {
853
+ if (timeout)
854
+ clearTimeout(timeout);
855
+ }
856
+ }
857
+ function isDomAbortError(error) {
858
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
859
+ }
860
+ function mergeSignals(a, b) {
861
+ if (!b)
862
+ return a;
863
+ const anyFn = AbortSignal.any;
864
+ if (typeof anyFn === "function")
865
+ return anyFn([a, b]);
866
+ const controller = new AbortController;
867
+ const onAbort = () => controller.abort();
868
+ if (a.aborted)
869
+ controller.abort();
870
+ else
871
+ a.addEventListener("abort", onAbort, { once: true });
872
+ if (b.aborted)
873
+ controller.abort();
874
+ else
875
+ b.addEventListener("abort", onAbort, { once: true });
876
+ return controller.signal;
877
+ }
878
+ async function readErrorResponse(response) {
879
+ const body = await response.text();
880
+ return {
881
+ body,
882
+ headers: getHeadersAsObject(response.headers),
883
+ status: response.status
884
+ };
885
+ }
886
+ function getRetryDelay(params) {
887
+ const { retryDelay, attempt, response } = params;
888
+ if (response && (response.status === 429 || response.status === 503)) {
889
+ const retryAfter = response.headers.get("retry-after");
890
+ if (retryAfter) {
891
+ const seconds = Number(retryAfter);
892
+ if (Number.isFinite(seconds))
893
+ return seconds * 1000;
894
+ const dateMs = Date.parse(retryAfter);
895
+ if (!Number.isNaN(dateMs)) {
896
+ return Math.max(0, dateMs - Date.now());
897
+ }
898
+ }
899
+ }
900
+ if (typeof retryDelay === "function")
901
+ return retryDelay(attempt);
902
+ return retryDelay;
903
+ }
904
+ function shouldAttemptRetryOnError(error) {
905
+ if (error instanceof HttpResponseError)
906
+ return false;
907
+ if (error instanceof ResponseValidationError)
908
+ return false;
909
+ if (error instanceof RequestValidationError)
910
+ return false;
911
+ if (error instanceof ConfigError)
912
+ return false;
913
+ if (error instanceof RequestAbortedError)
914
+ return false;
915
+ return true;
437
916
  }
438
- function defaultShouldRetry({ error, response }) {
917
+ function shouldAttemptRetryOnHttp(retryCtx) {
918
+ const { error, response } = retryCtx;
439
919
  if (error)
440
- return true;
441
- return response ? response.status >= 500 : false;
920
+ return shouldAttemptRetryOnError(error);
921
+ return response ? response.status >= 500 || response.status === 429 : false;
922
+ }
923
+ async function makeRequestWithRetry(args) {
924
+ const {
925
+ config,
926
+ maxRetries,
927
+ retryDelay,
928
+ validateFn,
929
+ shouldRetry,
930
+ logger,
931
+ ctx,
932
+ endpointName,
933
+ method,
934
+ fullUrl,
935
+ meta,
936
+ onResponse,
937
+ onRetry
938
+ } = args;
939
+ let retryCount = 0;
940
+ while (true) {
941
+ let response;
942
+ try {
943
+ logger?.debug?.("API request", config);
944
+ response = await httpRequest(config, (over) => ctx({ attempt: retryCount, ...over }));
945
+ await onResponse?.({
946
+ endpoint: endpointName,
947
+ method,
948
+ url: fullUrl,
949
+ response: response.clone(),
950
+ attempt: retryCount,
951
+ meta
952
+ });
953
+ if (response.status >= 400) {
954
+ const retryCtx = {
955
+ response,
956
+ retryCount,
957
+ maxRetries,
958
+ endpoint: endpointName,
959
+ method,
960
+ url: fullUrl
961
+ };
962
+ if (retryCount < maxRetries && shouldRetry(retryCtx)) {
963
+ const delayMs = getRetryDelay({
964
+ retryDelay,
965
+ attempt: retryCount,
966
+ response
967
+ });
968
+ await onRetry?.({
969
+ ...retryCtx,
970
+ delayMs,
971
+ nextAttempt: retryCount + 1
972
+ });
973
+ retryCount++;
974
+ await delay(delayMs);
975
+ continue;
976
+ }
977
+ const { body, headers: responseHeaders } = await readErrorResponse(response);
978
+ throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
979
+ }
980
+ return {
981
+ validatedData: await validateFn(response),
982
+ retryCount,
983
+ httpStatus: response.status
984
+ };
985
+ } catch (error) {
986
+ if (error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError) {
987
+ throw error;
988
+ }
989
+ const retryCtx = {
990
+ error,
991
+ retryCount,
992
+ maxRetries,
993
+ endpoint: endpointName,
994
+ method,
995
+ url: fullUrl
996
+ };
997
+ if (retryCount >= maxRetries || !shouldRetry(retryCtx)) {
998
+ throw error;
999
+ }
1000
+ const delayMs = getRetryDelay({
1001
+ retryDelay,
1002
+ attempt: retryCount,
1003
+ response: undefined
1004
+ });
1005
+ await onRetry?.({
1006
+ ...retryCtx,
1007
+ delayMs,
1008
+ nextAttempt: retryCount + 1
1009
+ });
1010
+ retryCount++;
1011
+ await delay(delayMs);
1012
+ }
1013
+ }
1014
+ }
1015
+ async function validateAndTransformResponse(endpoint, response, ctx) {
1016
+ const responseHeaders = getHeadersAsObject(response.headers);
1017
+ let responseData;
1018
+ if (hasJsonContentType(responseHeaders)) {
1019
+ const text = await response.text();
1020
+ try {
1021
+ responseData = text === "" ? undefined : JSON.parse(text);
1022
+ } catch (error) {
1023
+ throw new ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
1024
+ }
1025
+ } else {
1026
+ responseData = await response.text();
1027
+ }
1028
+ const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
1029
+ return validateResponse(endpoint.resSchema, transformedData, ctx);
1030
+ }
1031
+ function defaultShouldRetry(ctx) {
1032
+ if (ctx.error)
1033
+ return shouldAttemptRetryOnError(ctx.error);
1034
+ return shouldAttemptRetryOnHttp(ctx);
442
1035
  }
443
1036
  function delay(ms) {
444
1037
  return new Promise((resolve) => setTimeout(resolve, ms));
445
1038
  }
446
1039
 
1040
+ // src/api-wrapper/create-client.ts
1041
+ function isActionInvokeOptionsOnly(value) {
1042
+ if (value === null || value === undefined)
1043
+ return false;
1044
+ if (typeof value !== "object" || Array.isArray(value))
1045
+ return false;
1046
+ return Object.keys(value).every((k) => k === "signal" || k === "meta");
1047
+ }
1048
+ function createApiClientImpl(apiConfig) {
1049
+ let actions = {};
1050
+ const cache = createActionCache();
1051
+ const invokeEndpoint = async (endpointKey, input = {}) => {
1052
+ const endpoint = apiConfig.endpoints[endpointKey];
1053
+ const rawInput = input;
1054
+ const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
1055
+ const startedAt = Date.now();
1056
+ const maxRetries = input.retries ?? apiConfig.retries ?? 3;
1057
+ const baseContext = {
1058
+ endpoint: endpointName,
1059
+ method: endpoint?.method ?? "GET",
1060
+ url: "",
1061
+ attempt: 0,
1062
+ maxRetries,
1063
+ elapsedMs: 0,
1064
+ meta: input.meta
1065
+ };
1066
+ const elapsed = () => Date.now() - startedAt;
1067
+ const ctx = (over = {}) => ({
1068
+ ...baseContext,
1069
+ elapsedMs: elapsed(),
1070
+ ...over
1071
+ });
1072
+ if (!endpoint) {
1073
+ throw await reportApiError(new ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
1074
+ }
1075
+ baseContext.method = endpoint.method;
1076
+ let finalUrl;
1077
+ let fullUrl;
1078
+ try {
1079
+ const built = buildUrl(endpoint, rawInput.reqParams, rawInput.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url, (msg) => new ConfigError(msg, ctx()));
1080
+ finalUrl = built.finalUrl;
1081
+ fullUrl = built.fullUrl;
1082
+ } catch (e) {
1083
+ if (e instanceof ApiError)
1084
+ throw await reportApiError(e, apiConfig);
1085
+ throw e;
1086
+ }
1087
+ baseContext.url = fullUrl;
1088
+ let queryParams;
1089
+ let body;
1090
+ let headers;
1091
+ try {
1092
+ queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
1093
+ body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
1094
+ headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
1095
+ } catch (e) {
1096
+ if (e instanceof ApiError)
1097
+ throw await reportApiError(e, apiConfig);
1098
+ throw e;
1099
+ }
1100
+ baseContext.requestHeaders = headers;
1101
+ baseContext.requestBody = body;
1102
+ if (apiConfig.onRequest) {
1103
+ try {
1104
+ const result = await apiConfig.onRequest({
1105
+ endpoint: endpointName,
1106
+ method: endpoint.method,
1107
+ url: fullUrl,
1108
+ headers,
1109
+ body,
1110
+ meta: input.meta
1111
+ });
1112
+ if (result?.headers)
1113
+ headers = result.headers;
1114
+ if (result && "body" in result)
1115
+ body = result.body;
1116
+ } catch (e) {
1117
+ if (e instanceof ApiError)
1118
+ throw await reportApiError(e, apiConfig);
1119
+ throw e;
1120
+ }
1121
+ }
1122
+ const config = {
1123
+ method: endpoint.method,
1124
+ url: finalUrl,
1125
+ queryParams,
1126
+ body,
1127
+ headers,
1128
+ doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
1129
+ timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
1130
+ fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
1131
+ userSignal: input.signal
1132
+ };
1133
+ try {
1134
+ const result = await makeRequestWithRetry({
1135
+ config,
1136
+ maxRetries,
1137
+ retryDelay: apiConfig.retryDelayMs ?? 300,
1138
+ validateFn: (response) => validateAndTransformResponse(endpoint, response, ctx),
1139
+ shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
1140
+ logger: apiConfig.logger,
1141
+ ctx,
1142
+ endpointName,
1143
+ method: endpoint.method,
1144
+ fullUrl,
1145
+ meta: input.meta,
1146
+ onResponse: apiConfig.onResponse,
1147
+ onRetry: apiConfig.onRetry
1148
+ });
1149
+ const responseTimeMs = elapsed();
1150
+ const responseSizeMb = calculateSizeInMb(result.validatedData);
1151
+ const requestBodySizeMb = calculateSizeInMb(config.body);
1152
+ return {
1153
+ data: result.validatedData,
1154
+ requestBodySizeMb,
1155
+ responseSizeMb,
1156
+ responseTimeMs,
1157
+ httpStatus: result.httpStatus,
1158
+ retryCount: result.retryCount
1159
+ };
1160
+ } catch (error) {
1161
+ if (error instanceof ApiError)
1162
+ throw await reportApiError(error, apiConfig);
1163
+ throw error;
1164
+ }
1165
+ };
1166
+ async function invokeAction(name, rawInput, options = {}) {
1167
+ const action = actions[name];
1168
+ const inputForHandler = action.noRuntimeInput ? undefined : rawInput;
1169
+ const actionName = apiConfig.name ? `${apiConfig.name}.${name}` : name;
1170
+ const startedAt = Date.now();
1171
+ const baseContext = {
1172
+ endpoint: actionName,
1173
+ method: "GET",
1174
+ url: "",
1175
+ attempt: 0,
1176
+ maxRetries: 0,
1177
+ elapsedMs: 0,
1178
+ meta: options.meta,
1179
+ requestBody: action.noRuntimeInput ? undefined : rawInput
1180
+ };
1181
+ const ctx = (over = {}) => ({
1182
+ ...baseContext,
1183
+ elapsedMs: Date.now() - startedAt,
1184
+ ...over
1185
+ });
1186
+ apiConfig.logger?.debug?.("Action start", { action: actionName });
1187
+ try {
1188
+ const result = await action.handler({
1189
+ input: inputForHandler,
1190
+ api: proxy,
1191
+ signal: options.signal,
1192
+ meta: options.meta,
1193
+ logger: apiConfig.logger,
1194
+ cache
1195
+ });
1196
+ apiConfig.logger?.debug?.("Action end", {
1197
+ action: actionName,
1198
+ elapsedMs: Date.now() - startedAt
1199
+ });
1200
+ return result;
1201
+ } catch (error) {
1202
+ if (error instanceof ApiError) {
1203
+ throw await reportApiError(error, apiConfig);
1204
+ }
1205
+ const wrapped = new ActionError(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
1206
+ throw await reportApiError(wrapped, apiConfig);
1207
+ }
1208
+ }
1209
+ const request = async (key, input, options) => {
1210
+ if (key in actions) {
1211
+ const action = actions[key];
1212
+ if (action.noRuntimeInput) {
1213
+ if (input === undefined || input === null) {
1214
+ return invokeAction(key, undefined, options ?? {});
1215
+ }
1216
+ if (options !== undefined) {
1217
+ return invokeAction(key, undefined, options);
1218
+ }
1219
+ if (isActionInvokeOptionsOnly(input)) {
1220
+ return invokeAction(key, undefined, input);
1221
+ }
1222
+ return invokeAction(key, undefined, {});
1223
+ }
1224
+ return invokeAction(key, input, options);
1225
+ }
1226
+ return invokeEndpoint(key, input ?? {});
1227
+ };
1228
+ const client = { request, cache };
1229
+ const proxy = new Proxy(client, {
1230
+ get(target, prop, receiver) {
1231
+ if (prop in target || typeof prop !== "string") {
1232
+ return Reflect.get(target, prop, receiver);
1233
+ }
1234
+ if (prop in apiConfig.endpoints) {
1235
+ return (input) => invokeEndpoint(prop, input ?? {});
1236
+ }
1237
+ if (prop in actions) {
1238
+ const action = actions[prop];
1239
+ if (action.noRuntimeInput) {
1240
+ return (first, second) => {
1241
+ if (first === undefined || first === null) {
1242
+ return invokeAction(prop, undefined, second ?? {});
1243
+ }
1244
+ if (second !== undefined) {
1245
+ return invokeAction(prop, undefined, second);
1246
+ }
1247
+ if (isActionInvokeOptionsOnly(first)) {
1248
+ return invokeAction(prop, undefined, first);
1249
+ }
1250
+ return invokeAction(prop, undefined, {});
1251
+ };
1252
+ }
1253
+ return (input, opt) => invokeAction(prop, input, opt);
1254
+ }
1255
+ return;
1256
+ }
1257
+ });
1258
+ const rawActions = apiConfig.actions;
1259
+ if (typeof rawActions === "function") {
1260
+ actions = rawActions({
1261
+ api: proxy,
1262
+ defineAction: makeDefineAction()
1263
+ });
1264
+ } else if (rawActions) {
1265
+ actions = rawActions;
1266
+ }
1267
+ const collisions = Object.keys(actions).filter((key) => (key in apiConfig.endpoints));
1268
+ if (collisions.length > 0) {
1269
+ throw new ConfigError(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
1270
+ endpoint: collisions[0],
1271
+ method: "GET",
1272
+ url: "",
1273
+ attempt: 0,
1274
+ maxRetries: 0,
1275
+ elapsedMs: 0
1276
+ });
1277
+ }
1278
+ return proxy;
1279
+ }
1280
+ var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
447
1281
  // src/p-queue/index.ts
448
1282
  class TimeoutError extends Error {
449
1283
  constructor(message) {
@@ -1103,6 +1937,7 @@ var getDomainSitemap = async (domain, options = {}) => {
1103
1937
 
1104
1938
  // src/secrets/infisical.ts
1105
1939
  import { z as z4 } from "zod";
1940
+ var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
1106
1941
  var defaultFetch = globalThis.fetch;
1107
1942
  var infisicalAuthResponseSchema = z4.object({
1108
1943
  accessToken: z4.string()
@@ -1114,19 +1949,14 @@ var infisicalSecretSchema = z4.object({
1114
1949
  var infisicalSecretsResponseSchema = z4.object({
1115
1950
  secrets: z4.array(infisicalSecretSchema)
1116
1951
  });
1117
- async function loadInfisicalSecrets({
1952
+ async function fetchInfisicalSecrets({
1118
1953
  keys,
1119
1954
  projectId,
1120
1955
  environment,
1121
1956
  siteUrl,
1122
- clientIdEnvKey,
1123
- clientSecretEnvKey
1957
+ clientId,
1958
+ clientSecret
1124
1959
  }) {
1125
- const clientId = process.env[clientIdEnvKey];
1126
- const clientSecret = process.env[clientSecretEnvKey];
1127
- if (!clientId || !clientSecret) {
1128
- throw new Error("Missing Infisical credentials");
1129
- }
1130
1960
  const infisical = createInfisicalApiClient(siteUrl);
1131
1961
  const { data: auth } = await infisical.request("login", {
1132
1962
  reqBody: {
@@ -1150,6 +1980,28 @@ async function loadInfisicalSecrets({
1150
1980
  const wantedKeys = new Set(keys);
1151
1981
  return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
1152
1982
  }
1983
+ async function loadInfisicalSecrets({
1984
+ keys,
1985
+ projectId,
1986
+ environment,
1987
+ siteUrl,
1988
+ clientIdEnvKey,
1989
+ clientSecretEnvKey
1990
+ }) {
1991
+ const clientId = process.env[clientIdEnvKey];
1992
+ const clientSecret = process.env[clientSecretEnvKey];
1993
+ if (!clientId || !clientSecret) {
1994
+ throw new Error("Missing Infisical credentials");
1995
+ }
1996
+ return fetchInfisicalSecrets({
1997
+ keys,
1998
+ projectId,
1999
+ environment,
2000
+ siteUrl,
2001
+ clientId,
2002
+ clientSecret
2003
+ });
2004
+ }
1153
2005
  function createInfisicalApiClient(siteUrl) {
1154
2006
  return createApiClient({
1155
2007
  name: "infisical",
@@ -1193,6 +2045,48 @@ function getFetch() {
1193
2045
  return globalThis.fetch ?? defaultFetch;
1194
2046
  }
1195
2047
 
2048
+ // src/secrets/loaders.ts
2049
+ function cloudflareWorkerEnvLoader(workerEnv) {
2050
+ return async (ctx) => {
2051
+ const out = {};
2052
+ for (const key of ctx.keys) {
2053
+ const str = workerBindingToString(workerEnv[key]);
2054
+ if (str !== undefined) {
2055
+ out[key] = str;
2056
+ }
2057
+ }
2058
+ return out;
2059
+ };
2060
+ }
2061
+ function workerBindingToString(value) {
2062
+ if (value === undefined || value === null) {
2063
+ return;
2064
+ }
2065
+ if (typeof value === "string") {
2066
+ return value === "" ? undefined : value;
2067
+ }
2068
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
2069
+ return String(value);
2070
+ }
2071
+ if (typeof value === "object") {
2072
+ return JSON.stringify(value);
2073
+ }
2074
+ return String(value);
2075
+ }
2076
+ function infisicalLoader(config) {
2077
+ return async (ctx) => fetchInfisicalSecrets({
2078
+ keys: ctx.keys,
2079
+ projectId: config.projectId,
2080
+ environment: ctx.environment,
2081
+ siteUrl: config.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
2082
+ clientId: config.clientId,
2083
+ clientSecret: config.clientSecret
2084
+ });
2085
+ }
2086
+ async function processEnvLoader(_ctx) {
2087
+ return {};
2088
+ }
2089
+
1196
2090
  // src/secrets/vite.ts
1197
2091
  function getViteDefine(secretSet) {
1198
2092
  return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
@@ -1209,9 +2103,6 @@ function viteSecretSetPlugin(secretSet) {
1209
2103
  };
1210
2104
  }
1211
2105
 
1212
- // src/secrets/index.ts
1213
- import { networkInterfaces } from "node:os";
1214
-
1215
2106
  // src/cool-console-log/index.ts
1216
2107
  var terminalColors = {
1217
2108
  reset: "\x1B[0m",
@@ -1318,9 +2209,6 @@ function logBanner(text, color = "cyan", options = {}) {
1318
2209
  }
1319
2210
 
1320
2211
  // src/secrets/index.ts
1321
- var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
1322
- var LOCAL_IP_VALUE = "LOCAL_IP";
1323
- var VPN_INTERFACE_RE = /^(utun|tun|tailscale|wg|ipsec|ppp)/i;
1324
2212
  var logger = createCoolLogger();
1325
2213
  function getSecretEnvironment() {
1326
2214
  const env = process.env.SECRETS_ENV || "development";
@@ -1332,61 +2220,91 @@ function getSecretEnvironment() {
1332
2220
  }
1333
2221
  async function defineSecretSet(keys, options) {
1334
2222
  const keySet = [...new Set(keys)];
1335
- const loader = options.loader ?? loadInfisicalSecrets;
1336
- const getLocalIp = options.getLocalIp ?? getLocalIpAddress;
2223
+ const resolvedOptions = resolveSecretSetOptions(options);
1337
2224
  let cachedSecrets = null;
1338
- const resolveValue = (value) => {
1339
- if (value === LOCAL_IP_VALUE)
1340
- return getLocalIp();
1341
- return value;
1342
- };
1343
2225
  const loadSecrets = async (force = false) => {
1344
- const envSecrets = readEnvSecrets(keySet, force ? cachedSecrets : null);
1345
- const keysMissingFromEnv = keySet.filter((key) => !envSecrets[key]);
1346
- const environment = resolveEnvironment(options.environment);
1347
- logSecretEnvironment(environment, keySet.length);
1348
- const loadedSecrets = keysMissingFromEnv.length === 0 && !force ? {} : await loader({
1349
- keys: keySet,
1350
- projectId: options.projectId,
1351
- environment,
1352
- siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
1353
- clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
1354
- clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
2226
+ const nextSecrets = await loadAndMergeSecrets({
2227
+ keySet,
2228
+ cachedSecrets,
2229
+ force,
2230
+ ...resolvedOptions
1355
2231
  });
1356
- const missingKeys = keySet.filter((key) => !(envSecrets[key] ?? loadedSecrets[key]));
1357
- if (missingKeys.length > 0) {
1358
- throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
1359
- }
1360
- const nextSecrets = Object.fromEntries(keySet.map((key) => [
1361
- key,
1362
- resolveValue(envSecrets[key] ?? loadedSecrets[key])
1363
- ]));
1364
- for (const [key, value] of Object.entries(nextSecrets)) {
1365
- process.env[key] = value;
1366
- }
1367
2232
  cachedSecrets = nextSecrets;
1368
2233
  return nextSecrets;
1369
2234
  };
2235
+ const secretSet = createSecretSet({
2236
+ getCachedSecrets: () => cachedSecrets,
2237
+ reload: loadSecrets
2238
+ });
2239
+ await loadSecrets();
2240
+ return secretSet;
2241
+ }
2242
+ function resolveSecretSetOptions(options) {
2243
+ const environment = resolveEnvironment(options.environment);
2244
+ const loader = options.loader ? options.loader : (context) => loadInfisicalSecrets({
2245
+ ...context,
2246
+ projectId: options.projectId,
2247
+ siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
2248
+ clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
2249
+ clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
2250
+ });
2251
+ return {
2252
+ loader,
2253
+ environment
2254
+ };
2255
+ }
2256
+ async function loadAndMergeSecrets({
2257
+ keySet,
2258
+ cachedSecrets,
2259
+ loader,
2260
+ environment,
2261
+ force
2262
+ }) {
2263
+ const processEnvSecrets = readProcessEnvSecrets(keySet, force ? cachedSecrets : null);
2264
+ const keysMissingFromEnv = keySet.filter((key) => !processEnvSecrets[key]);
2265
+ logSecretEnvironment(environment, keySet.length);
2266
+ const loaderKeys = force ? keySet : keysMissingFromEnv;
2267
+ const loadedSecrets = loaderKeys.length === 0 ? {} : await loader({
2268
+ keys: loaderKeys,
2269
+ environment
2270
+ });
2271
+ const missingKeys = keySet.filter((key) => !(processEnvSecrets[key] ?? loadedSecrets[key]));
2272
+ if (missingKeys.length > 0) {
2273
+ throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
2274
+ }
2275
+ const nextSecrets = Object.fromEntries(keySet.map((key) => [
2276
+ key,
2277
+ processEnvSecrets[key] ?? loadedSecrets[key]
2278
+ ]));
2279
+ for (const [key, value] of Object.entries(nextSecrets)) {
2280
+ process.env[key] = value;
2281
+ }
2282
+ return nextSecrets;
2283
+ }
2284
+ function createSecretSet({
2285
+ getCachedSecrets,
2286
+ reload
2287
+ }) {
1370
2288
  const secretSet = {
1371
2289
  secret(key) {
1372
- const value = cachedSecrets?.[key] ?? process.env[key];
2290
+ const value = getCachedSecrets()?.[key] ?? process.env[key];
1373
2291
  if (!value) {
1374
2292
  throw new Error(`Secret ${key} not found`);
1375
2293
  }
1376
- return resolveValue(value);
2294
+ return value;
1377
2295
  },
1378
2296
  secrets() {
2297
+ const cachedSecrets = getCachedSecrets();
1379
2298
  if (!cachedSecrets) {
1380
2299
  throw new Error("Secrets not initialized.");
1381
2300
  }
1382
- return cachedSecrets;
2301
+ return { ...cachedSecrets };
1383
2302
  },
1384
2303
  async reload() {
1385
- await loadSecrets(true);
2304
+ await reload(true);
1386
2305
  return secretSet;
1387
2306
  }
1388
2307
  };
1389
- await loadSecrets();
1390
2308
  return secretSet;
1391
2309
  }
1392
2310
  function resolveEnvironment(environment) {
@@ -1400,28 +2318,12 @@ function logSecretEnvironment(environment, secretCount) {
1400
2318
  count: secretCount
1401
2319
  });
1402
2320
  }
1403
- function readEnvSecrets(keys, previousSecrets = null) {
2321
+ function readProcessEnvSecrets(keys, previousSecrets = null) {
1404
2322
  return Object.fromEntries(keys.filter((key) => {
1405
2323
  const value = process.env[key];
1406
2324
  return value && value !== previousSecrets?.[key];
1407
2325
  }).map((key) => [key, process.env[key]]));
1408
2326
  }
1409
- function getLocalIpAddress() {
1410
- const interfaces = networkInterfaces();
1411
- for (const name of Object.keys(interfaces)) {
1412
- if (VPN_INTERFACE_RE.test(name))
1413
- continue;
1414
- const nets = interfaces[name];
1415
- if (!nets)
1416
- continue;
1417
- for (const net of nets) {
1418
- if (net.family === "IPv4" && !net.internal) {
1419
- return net.address;
1420
- }
1421
- }
1422
- }
1423
- return "127.0.0.1";
1424
- }
1425
2327
 
1426
2328
  // src/typedswitch/index.ts
1427
2329
  function runSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
@@ -2074,6 +2976,7 @@ export {
2074
2976
  seriesColorKey,
2075
2977
  resolveSeriesColorMap,
2076
2978
  promiseTimeout,
2979
+ processEnvLoader,
2077
2980
  pickItemsInArray,
2078
2981
  partition,
2079
2982
  normalizeSingleOrArray,
@@ -2086,7 +2989,17 @@ export {
2086
2989
  jwtSign,
2087
2990
  jsonToZodSchemaMapper,
2088
2991
  isSuccess,
2992
+ isResponseValidationError,
2993
+ isRequestValidationError,
2994
+ isRequestTimeoutError,
2995
+ isRequestAbortedError,
2996
+ isNetworkError,
2997
+ isHttpResponseError,
2089
2998
  isFailure,
2999
+ isConfigError,
3000
+ isApiError,
3001
+ isActionError,
3002
+ infisicalLoader,
2090
3003
  hashStringToColorIndex,
2091
3004
  groupBy,
2092
3005
  getViteDefine,
@@ -2100,15 +3013,17 @@ export {
2100
3013
  generateId,
2101
3014
  formatPeriodLabel,
2102
3015
  findArrayDifferenceByKey,
3016
+ fetchInfisicalSecrets,
2103
3017
  extractNumber,
2104
3018
  defineSecretSet,
3019
+ defineAction,
2105
3020
  createZId,
2106
3021
  createResolveColorsByKeys,
2107
3022
  createCoolLogger,
2108
- createApiWrapper,
2109
3023
  createApiClient,
2110
3024
  compact,
2111
3025
  colorize,
3026
+ cloudflareWorkerEnvLoader,
2112
3027
  chunkArray,
2113
3028
  cacheFunction,
2114
3029
  backgroundPromiseSync,
@@ -2116,7 +3031,15 @@ export {
2116
3031
  TimeoutError,
2117
3032
  SimpleStateMachine,
2118
3033
  ResponseValidationError,
3034
+ RequestValidationError,
3035
+ RequestTimeoutError,
3036
+ RequestAbortedError,
2119
3037
  PriorityQueue,
3038
+ NetworkError,
2120
3039
  METRICS_SERIES_PALETTE_SIZE,
2121
- HttpResponseError
3040
+ HttpResponseError,
3041
+ DEFAULT_INFISICAL_SITE_URL,
3042
+ ConfigError,
3043
+ ApiError,
3044
+ ActionError
2122
3045
  };