hanzio 1.0.10 → 1.2.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 (71) hide show
  1. package/README.md +70 -4
  2. package/dist/api-wrapper/actions-impl.d.ts +4 -0
  3. package/dist/api-wrapper/builders.d.ts +23 -0
  4. package/dist/api-wrapper/create-client.d.ts +6 -0
  5. package/dist/api-wrapper/error-reporting.d.ts +7 -0
  6. package/dist/api-wrapper/errors.d.ts +64 -0
  7. package/dist/api-wrapper/headers.d.ts +10 -0
  8. package/dist/api-wrapper/index.d.ts +5 -84
  9. package/dist/api-wrapper/redaction.d.ts +2 -0
  10. package/dist/api-wrapper/shared.d.ts +16 -0
  11. package/dist/api-wrapper/size.d.ts +1 -0
  12. package/dist/api-wrapper/test-helpers.d.ts +6 -0
  13. package/dist/api-wrapper/transport.d.ts +55 -0
  14. package/dist/api-wrapper/types.d.ts +154 -0
  15. package/dist/api-wrapper/zod-issues.d.ts +17 -0
  16. package/dist/cache/index.d.ts +26 -0
  17. package/dist/secrets/core.d.ts +47 -0
  18. package/dist/secrets/deadline.d.ts +6 -0
  19. package/dist/secrets/environment.d.ts +9 -0
  20. package/dist/secrets/errors.d.ts +11 -0
  21. package/dist/secrets/index.d.ts +14 -23
  22. package/dist/secrets/infisical-cli.d.ts +6 -0
  23. package/dist/secrets/infisical-config.d.ts +17 -0
  24. package/dist/secrets/infisical-session.d.ts +4 -0
  25. package/dist/secrets/infisical.d.ts +14 -2
  26. package/dist/secrets/loaders.d.ts +9 -0
  27. package/dist/secrets/vite.d.ts +7 -3
  28. package/dist/secrets/worker-loader.d.ts +9 -0
  29. package/dist/secrets/worker.d.ts +3 -0
  30. package/dist/src/api-wrapper/index.js +1059 -177
  31. package/dist/src/index.js +2807 -1397
  32. package/dist/src/jwt/index.js +2 -2
  33. package/dist/src/p-queue/index.js +3 -3
  34. package/dist/src/secrets/index.js +1610 -407
  35. package/dist/src/secrets/infisical.js +1352 -214
  36. package/dist/src/secrets/loaders.js +1529 -0
  37. package/dist/src/secrets/vite.js +33 -11
  38. package/dist/src/secrets/worker.js +275 -0
  39. package/dist/src/sitemap/index.js +1 -1
  40. package/dist/src/zod/index.js +7 -7
  41. package/dist/typedswitch/index.d.ts +9 -5
  42. package/package.json +12 -3
  43. package/src/api-wrapper/actions-impl.ts +100 -0
  44. package/src/api-wrapper/builders.ts +200 -0
  45. package/src/api-wrapper/create-client.ts +393 -0
  46. package/src/api-wrapper/error-reporting.ts +31 -0
  47. package/src/api-wrapper/errors.ts +231 -0
  48. package/src/api-wrapper/headers.ts +79 -0
  49. package/src/api-wrapper/index.ts +5 -492
  50. package/src/api-wrapper/redaction.ts +62 -0
  51. package/src/api-wrapper/shared.ts +25 -0
  52. package/src/api-wrapper/size.ts +19 -0
  53. package/src/api-wrapper/test-helpers.ts +25 -0
  54. package/src/api-wrapper/transport.ts +350 -0
  55. package/src/api-wrapper/types.ts +259 -0
  56. package/src/api-wrapper/zod-issues.ts +157 -0
  57. package/src/cache/index.ts +228 -61
  58. package/src/secrets/core.ts +348 -0
  59. package/src/secrets/deadline.ts +69 -0
  60. package/src/secrets/environment.ts +69 -0
  61. package/src/secrets/errors.ts +29 -0
  62. package/src/secrets/index.ts +81 -140
  63. package/src/secrets/infisical-cli.ts +99 -0
  64. package/src/secrets/infisical-config.ts +70 -0
  65. package/src/secrets/infisical-session.ts +42 -0
  66. package/src/secrets/infisical.ts +219 -35
  67. package/src/secrets/loaders.ts +43 -0
  68. package/src/secrets/vite.ts +44 -15
  69. package/src/secrets/worker-loader.ts +56 -0
  70. package/src/secrets/worker.ts +4 -0
  71. package/src/typedswitch/index.ts +80 -69
@@ -1,250 +1,1132 @@
1
- // src/api-wrapper/index.ts
1
+ // src/api-wrapper/headers.ts
2
+ var CANONICAL_HEADER_NAMES = {
3
+ "content-type": "Content-Type",
4
+ authorization: "Authorization",
5
+ accept: "Accept",
6
+ "user-agent": "User-Agent",
7
+ "x-request-id": "X-Request-Id",
8
+ "x-correlation-id": "X-Correlation-Id"
9
+ };
10
+ function normalizeHeaders(headers) {
11
+ const result = {};
12
+ const lowerToCanonical = new Map;
13
+ for (const [key, value] of Object.entries(headers)) {
14
+ const lower = key.toLowerCase();
15
+ const canonical = CANONICAL_HEADER_NAMES[lower] ?? key;
16
+ const existing = lowerToCanonical.get(lower);
17
+ if (existing && existing !== canonical)
18
+ delete result[existing];
19
+ lowerToCanonical.set(lower, canonical);
20
+ result[canonical] = value;
21
+ }
22
+ return result;
23
+ }
24
+ function getHeader(headers, name) {
25
+ const lower = name.toLowerCase();
26
+ const canonical = CANONICAL_HEADER_NAMES[lower];
27
+ if (canonical !== undefined) {
28
+ if (headers[canonical] !== undefined)
29
+ return headers[canonical];
30
+ }
31
+ for (const [k, v] of Object.entries(headers)) {
32
+ if (k.toLowerCase() === lower)
33
+ return v;
34
+ }
35
+ return;
36
+ }
37
+ function hasJsonContentType(headers) {
38
+ const ct = getHeader(headers, "content-type") ?? "";
39
+ return ct.includes("application/json");
40
+ }
41
+ function buildFetchBody(body, headers, configError) {
42
+ if (body === undefined || body === null)
43
+ return;
44
+ if (body instanceof FormData || body instanceof Blob)
45
+ return body;
46
+ if (hasJsonContentType(headers))
47
+ return JSON.stringify(body);
48
+ if (typeof body === "object" && (Object.getPrototypeOf(body) === Object.prototype || Object.getPrototypeOf(body) === null) && configError) {
49
+ throw configError("Request body is a plain object but Content-Type is not application/json");
50
+ }
51
+ return body;
52
+ }
53
+ function getHeadersAsObject(headers) {
54
+ const result = {};
55
+ headers.forEach((value, key) => {
56
+ result[key] = value;
57
+ });
58
+ return result;
59
+ }
60
+
61
+ // src/api-wrapper/zod-issues.ts
2
62
  import { ZodError } from "zod";
63
+ function formatZodIssues(error, rawData) {
64
+ return error.issues.map((issue) => {
65
+ const pathArray = [...issue.path];
66
+ const path = formatIssuePath(pathArray);
67
+ const value = getValueAtPath(rawData, pathArray);
68
+ const valuePreview = value === undefined ? undefined : previewValue(value);
69
+ const raw = issue;
70
+ const expected = typeof raw.expected === "string" ? raw.expected : undefined;
71
+ const received = typeof raw.received === "string" ? raw.received : value !== undefined ? typeofVerbose(value) : undefined;
72
+ const known = new Set([
73
+ "code",
74
+ "path",
75
+ "message",
76
+ "expected",
77
+ "received",
78
+ "fatal"
79
+ ]);
80
+ const extra = {};
81
+ for (const [k, v] of Object.entries(raw)) {
82
+ if (!known.has(k) && v !== undefined)
83
+ extra[k] = v;
84
+ }
85
+ return {
86
+ path,
87
+ pathArray,
88
+ code: issue.code,
89
+ message: issue.message,
90
+ expected,
91
+ received,
92
+ value,
93
+ valuePreview,
94
+ extra: Object.keys(extra).length > 0 ? extra : undefined
95
+ };
96
+ });
97
+ }
98
+ function renderIssueSummary(issues) {
99
+ if (issues.length === 0)
100
+ return "(no issues reported)";
101
+ return issues.map((issue, idx) => {
102
+ const parts = [];
103
+ parts.push(` [${idx + 1}] ${issue.path}: ${issue.message}`);
104
+ if (issue.expected || issue.received) {
105
+ const exp = issue.expected ? `expected ${issue.expected}` : "";
106
+ const rec = issue.received ? `received ${issue.received}` : "";
107
+ parts.push(` (${[exp, rec].filter(Boolean).join(", ")})`);
108
+ }
109
+ if (issue.valuePreview !== undefined) {
110
+ parts.push(` value: ${issue.valuePreview}`);
111
+ }
112
+ if (issue.extra) {
113
+ for (const [k, v] of Object.entries(issue.extra)) {
114
+ parts.push(` ${k}: ${previewValue(v, 120)}`);
115
+ }
116
+ }
117
+ return parts.join(`
118
+ `);
119
+ }).join(`
120
+ `);
121
+ }
122
+ function formatIssuePath(pathArray) {
123
+ if (pathArray.length === 0)
124
+ return "<root>";
125
+ let out = "";
126
+ for (const segment of pathArray) {
127
+ if (typeof segment === "number") {
128
+ out += `[${segment}]`;
129
+ } else if (/^[A-Za-z_$][\w$]*$/.test(segment)) {
130
+ out += out === "" ? segment : `.${segment}`;
131
+ } else {
132
+ out += `[${JSON.stringify(segment)}]`;
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+ function getValueAtPath(data, pathArray) {
138
+ let current = data;
139
+ for (const segment of pathArray) {
140
+ if (current === null || current === undefined)
141
+ return;
142
+ if (typeof current !== "object")
143
+ return;
144
+ current = current[segment];
145
+ }
146
+ return current;
147
+ }
148
+ function typeofVerbose(value) {
149
+ if (value === null)
150
+ return "null";
151
+ if (Array.isArray(value))
152
+ return "array";
153
+ return typeof value;
154
+ }
155
+ function previewValue(value, maxLen = 200) {
156
+ let str;
157
+ try {
158
+ if (value === undefined)
159
+ str = "undefined";
160
+ else if (typeof value === "string")
161
+ str = JSON.stringify(value);
162
+ else
163
+ str = JSON.stringify(value);
164
+ } catch {
165
+ str = String(value);
166
+ }
167
+ if (str === undefined)
168
+ str = String(value);
169
+ if (str.length > maxLen)
170
+ str = `${str.slice(0, maxLen)}…`;
171
+ return str;
172
+ }
173
+ function makeJsonParseZodError(error, text) {
174
+ const message = error instanceof Error ? `Response body was not valid JSON: ${error.message}` : "Response body was not valid JSON";
175
+ const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;
176
+ return new ZodError([
177
+ {
178
+ code: "custom",
179
+ path: [],
180
+ message: `${message} (body preview: ${JSON.stringify(preview)})`,
181
+ input: text
182
+ }
183
+ ]);
184
+ }
185
+ function tryParseJson(body) {
186
+ if (!body)
187
+ return;
188
+ try {
189
+ return JSON.parse(body);
190
+ } catch {
191
+ return;
192
+ }
193
+ }
3
194
 
4
- class ResponseValidationError extends Error {
195
+ // src/api-wrapper/errors.ts
196
+ var MAX_BODY_PREVIEW = 500;
197
+
198
+ class ApiError extends Error {
199
+ context;
200
+ cause;
201
+ constructor(message, context, options) {
202
+ super(message);
203
+ this.name = "ApiError";
204
+ this.context = context;
205
+ this.cause = options?.cause;
206
+ }
207
+ toJSON() {
208
+ return {
209
+ name: this.name,
210
+ message: this.message,
211
+ context: this.context
212
+ };
213
+ }
214
+ }
215
+
216
+ class HttpResponseError extends ApiError {
217
+ status;
218
+ body;
219
+ bodyJson;
220
+ responseHeaders;
221
+ requestId;
222
+ constructor(status, body, responseHeaders, context) {
223
+ const preview = body.length > MAX_BODY_PREVIEW ? `${body.slice(0, MAX_BODY_PREVIEW)}…` : body;
224
+ super(`HTTP ${status} ${context.method} ${context.url} - ${preview}`, context);
225
+ this.name = "HttpResponseError";
226
+ this.status = status;
227
+ this.body = body;
228
+ this.responseHeaders = responseHeaders;
229
+ this.requestId = getHeader(responseHeaders, "x-request-id") ?? getHeader(responseHeaders, "x-correlation-id");
230
+ this.bodyJson = tryParseJson(body);
231
+ }
232
+ toJSON() {
233
+ return {
234
+ ...super.toJSON(),
235
+ status: this.status,
236
+ body: this.body,
237
+ bodyJson: this.bodyJson,
238
+ requestId: this.requestId,
239
+ responseHeaders: this.responseHeaders
240
+ };
241
+ }
242
+ }
243
+
244
+ class ResponseValidationError extends ApiError {
5
245
  zodError;
6
246
  rawResponse;
247
+ issues;
7
248
  validationIssues;
8
- constructor(zodError, rawResponse) {
9
- const issues = zodError.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
10
- super(`Response validation failed: ${issues}`);
249
+ constructor(zodError, rawResponse, context) {
250
+ const issues = formatZodIssues(zodError, rawResponse);
251
+ const summary = renderIssueSummary(issues);
252
+ super(`Response validation failed (${context.method} ${context.url}):
253
+ ${summary}`, context, { cause: zodError });
11
254
  this.name = "ResponseValidationError";
12
255
  this.zodError = zodError;
13
256
  this.rawResponse = rawResponse;
14
- this.validationIssues = issues;
257
+ this.issues = issues;
258
+ this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
259
+ }
260
+ toJSON() {
261
+ return {
262
+ ...super.toJSON(),
263
+ issues: this.issues,
264
+ validationIssues: this.validationIssues,
265
+ rawResponsePreview: previewValue(this.rawResponse, 1000)
266
+ };
15
267
  }
16
268
  }
17
269
 
18
- class HttpResponseError extends Error {
19
- status;
20
- body;
21
- constructor(status, body) {
22
- super(`HTTP error: ${status} - ${body}`);
23
- this.name = "HttpResponseError";
24
- this.status = status;
25
- this.body = body;
270
+ class RequestValidationError extends ApiError {
271
+ zodError;
272
+ target;
273
+ rawInput;
274
+ issues;
275
+ validationIssues;
276
+ constructor(zodError, target, rawInput, context) {
277
+ const issues = formatZodIssues(zodError, rawInput);
278
+ const summary = renderIssueSummary(issues);
279
+ super(`Request ${target} validation failed (${context.method} ${context.url}):
280
+ ${summary}`, context, { cause: zodError });
281
+ this.name = "RequestValidationError";
282
+ this.zodError = zodError;
283
+ this.target = target;
284
+ this.rawInput = rawInput;
285
+ this.issues = issues;
286
+ this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
287
+ }
288
+ toJSON() {
289
+ return {
290
+ ...super.toJSON(),
291
+ target: this.target,
292
+ issues: this.issues,
293
+ validationIssues: this.validationIssues
294
+ };
26
295
  }
27
296
  }
28
- function createApiClient(apiConfig) {
29
- const request = async (endpointKey, input = {}) => {
30
- const endpoint = apiConfig.endpoints[endpointKey];
31
- if (!endpoint) {
32
- throw new Error(`Unknown API endpoint: ${String(endpointKey)}`);
297
+
298
+ class RequestTimeoutError extends ApiError {
299
+ timeoutMs;
300
+ constructor(timeoutMs, context) {
301
+ super(`Request timed out after ${timeoutMs}ms: ${context.method} ${context.url}`, context);
302
+ this.name = "RequestTimeoutError";
303
+ this.timeoutMs = timeoutMs;
304
+ }
305
+ }
306
+
307
+ class NetworkError extends ApiError {
308
+ constructor(message, context, cause) {
309
+ super(message, context, { cause });
310
+ this.name = "NetworkError";
311
+ }
312
+ }
313
+
314
+ class RequestAbortedError extends ApiError {
315
+ constructor(context, cause) {
316
+ super(`Request aborted: ${context.method} ${context.url}`, context, {
317
+ cause
318
+ });
319
+ this.name = "RequestAbortedError";
320
+ }
321
+ }
322
+
323
+ class ConfigError extends ApiError {
324
+ constructor(message, context) {
325
+ super(message, context);
326
+ this.name = "ConfigError";
327
+ }
328
+ }
329
+
330
+ class ActionError extends ApiError {
331
+ constructor(message, context, cause) {
332
+ super(message, context, { cause });
333
+ this.name = "ActionError";
334
+ }
335
+ }
336
+ var isApiError = (e) => e instanceof ApiError;
337
+ var isHttpResponseError = (e) => e instanceof HttpResponseError;
338
+ var isResponseValidationError = (e) => e instanceof ResponseValidationError;
339
+ var isRequestValidationError = (e) => e instanceof RequestValidationError;
340
+ var isRequestTimeoutError = (e) => e instanceof RequestTimeoutError;
341
+ var isNetworkError = (e) => e instanceof NetworkError;
342
+ var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
343
+ var isConfigError = (e) => e instanceof ConfigError;
344
+ var isActionError = (e) => e instanceof ActionError;
345
+ function isNonRetryableApiError(error) {
346
+ return error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError;
347
+ }
348
+ // src/api-wrapper/builders.ts
349
+ import { ZodError as ZodError2 } from "zod";
350
+ function formatQueryString(queryParams, doNotEncode) {
351
+ if (Object.keys(queryParams).length === 0)
352
+ return "";
353
+ return doNotEncode ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(queryParams).toString();
354
+ }
355
+ function buildUrl(options) {
356
+ const {
357
+ endpoint,
358
+ params,
359
+ queryParams,
360
+ baseApiUrls,
361
+ defaultBaseApiUrl,
362
+ url,
363
+ doNotEncodeQueryParams,
364
+ configError
365
+ } = options;
366
+ let finalUrl;
367
+ if (url) {
368
+ finalUrl = replacePathParams(url, params, configError);
369
+ } else {
370
+ const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
371
+ if (!defaultBaseUrl) {
372
+ throw configError("At least one base API URL is required");
33
373
  }
34
- const { finalUrl, fullUrl } = buildUrl(endpoint, input.reqParams, input.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url);
35
- const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
36
- const startedAt = Date.now();
37
- let retryCount = 0;
38
- let httpStatus = 0;
39
- apiConfig.onRequestStart?.({ endpoint: endpointName, url: fullUrl });
374
+ const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
375
+ const configBaseUrl = baseApiUrls[baseUrlKey];
376
+ if (!configBaseUrl) {
377
+ throw configError(`Unknown base API URL: ${baseUrlKey}`);
378
+ }
379
+ const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
380
+ const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
381
+ finalUrl = `${baseUrl}${replacePathParams(path, params, configError)}`;
382
+ }
383
+ const queryString = formatQueryString(queryParams, doNotEncodeQueryParams);
384
+ return {
385
+ finalUrl,
386
+ fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
387
+ };
388
+ }
389
+ function buildPathParams(endpoint, params, ctx) {
390
+ if (!endpoint.reqParamsSchema)
391
+ return params;
392
+ try {
393
+ return endpoint.reqParamsSchema.parse(params);
394
+ } catch (error) {
395
+ if (error instanceof ZodError2) {
396
+ throw new RequestValidationError(error, "params", params, ctx());
397
+ }
398
+ throw error;
399
+ }
400
+ }
401
+ function buildQueryParams(endpoint, query, ctx) {
402
+ const defaultParams = endpoint.reqDefaultQueryParams ?? {};
403
+ let parsedQuery;
404
+ if (endpoint.reqQuerySchema) {
40
405
  try {
41
- const config = {
42
- method: endpoint.method,
43
- url: finalUrl,
44
- queryParams: buildQueryParams(endpoint, input.reqQuery),
45
- body: buildRequestBody(endpoint, input.reqBody),
46
- headers: buildHeaders(endpoint, input.reqHeaders, apiConfig.defaultHeaders ?? {}),
47
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
48
- timeoutMs: apiConfig.timeoutMs,
49
- fetchFn: apiConfig.fetch ?? fetch
50
- };
51
- const result = await makeRequestWithRetry(config, apiConfig.retries ?? 3, apiConfig.retryDelayMs ?? 300, (response) => validateAndTransformResponse(endpoint, response), apiConfig.shouldRetry ?? defaultShouldRetry, apiConfig.logger);
52
- retryCount = result.retryCount;
53
- httpStatus = result.httpStatus;
54
- const responseTimeMs = Date.now() - startedAt;
55
- const responseSizeMb = calculateSizeInMb(result.validatedData);
56
- const requestSizeMb = calculateSizeInMb({
57
- method: config.method,
58
- url: fullUrl,
59
- body: config.body,
60
- headers: config.headers
61
- });
62
- apiConfig.onRequestEnd?.({
63
- endpoint: endpointName,
64
- url: fullUrl,
65
- status: httpStatus,
66
- retryCount,
67
- responseTimeMs
68
- });
69
- return {
70
- data: result.validatedData,
71
- requestSizeMb,
72
- responseSizeMb,
73
- requestSizeTotalMb: requestSizeMb + responseSizeMb,
74
- responseTimeMs,
75
- httpStatus,
76
- retryCount
77
- };
406
+ parsedQuery = endpoint.reqQuerySchema.parse(query);
407
+ } catch (error) {
408
+ if (error instanceof ZodError2) {
409
+ throw new RequestValidationError(error, "query", query, ctx());
410
+ }
411
+ throw error;
412
+ }
413
+ } else {
414
+ parsedQuery = query ?? {};
415
+ }
416
+ const merged = { ...defaultParams, ...parsedQuery };
417
+ return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
418
+ }
419
+ function buildRequestBody(endpoint, body, ctx) {
420
+ if (!endpoint.reqBodySchema)
421
+ return body;
422
+ let parsedBody;
423
+ try {
424
+ parsedBody = endpoint.reqBodySchema.parse(body);
425
+ } catch (error) {
426
+ if (error instanceof ZodError2) {
427
+ throw new RequestValidationError(error, "body", body, ctx());
428
+ }
429
+ throw error;
430
+ }
431
+ if (endpoint.reqBodyFormat !== "form-data") {
432
+ return parsedBody;
433
+ }
434
+ const formData = new FormData;
435
+ for (const [key, value] of Object.entries(parsedBody)) {
436
+ if (value == null)
437
+ continue;
438
+ formData.set(key, value instanceof Blob ? value : String(value));
439
+ }
440
+ return formData;
441
+ }
442
+ function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
443
+ const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
444
+ let parsedHeaders;
445
+ if (endpoint.reqHeadersSchema) {
446
+ try {
447
+ parsedHeaders = endpoint.reqHeadersSchema.parse(headers);
448
+ } catch (error) {
449
+ if (error instanceof ZodError2) {
450
+ throw new RequestValidationError(error, "headers", headers, ctx());
451
+ }
452
+ throw error;
453
+ }
454
+ } else {
455
+ parsedHeaders = headers ?? {};
456
+ }
457
+ return normalizeHeaders({
458
+ ...defaultHeaders,
459
+ ...contentType && { "Content-Type": contentType },
460
+ ...endpoint.defaultHeaders,
461
+ ...parsedHeaders
462
+ });
463
+ }
464
+ function replacePathParams(path, params, configError) {
465
+ return path.replace(/:(\w+)/g, (_, key) => {
466
+ const value = params?.[key];
467
+ if (value === undefined) {
468
+ throw configError(`Missing required path parameter: ${key}`);
469
+ }
470
+ return encodeURIComponent(String(value));
471
+ });
472
+ }
473
+
474
+ // src/api-wrapper/actions-impl.ts
475
+ function createActionCache() {
476
+ const store = new Map;
477
+ const isExpired = (entry) => entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
478
+ const cache = async (key, fn, options) => {
479
+ const existing = store.get(key);
480
+ if (existing && !isExpired(existing)) {
481
+ if (existing.inflight)
482
+ return existing.inflight;
483
+ if (existing.hasValue)
484
+ return existing.value;
485
+ }
486
+ const promise = (async () => fn())();
487
+ const entry = {
488
+ hasValue: false,
489
+ inflight: promise,
490
+ expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
491
+ };
492
+ store.set(key, entry);
493
+ try {
494
+ const value = await promise;
495
+ entry.value = value;
496
+ entry.hasValue = true;
497
+ entry.inflight = undefined;
498
+ return value;
78
499
  } catch (error) {
79
- apiConfig.logger?.error?.(error);
500
+ store.delete(key);
80
501
  throw error;
81
502
  }
82
503
  };
83
- return { request };
504
+ cache.get = (key) => {
505
+ const entry = store.get(key);
506
+ if (!entry || !entry.hasValue)
507
+ return;
508
+ if (isExpired(entry)) {
509
+ store.delete(key);
510
+ return;
511
+ }
512
+ return entry.value;
513
+ };
514
+ cache.set = (key, value, options) => {
515
+ store.set(key, {
516
+ value,
517
+ hasValue: true,
518
+ expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
519
+ });
520
+ };
521
+ cache.invalidate = (key) => {
522
+ store.delete(key);
523
+ };
524
+ cache.clear = () => {
525
+ store.clear();
526
+ };
527
+ return cache;
84
528
  }
85
- var createApiWrapper = createApiClient;
86
- async function validateResponse(schema, data) {
529
+ function makeDefineAction() {
530
+ function defineAction(...args) {
531
+ if (args.length === 0) {
532
+ return (def) => ({
533
+ ...def,
534
+ noRuntimeInput: false
535
+ });
536
+ }
537
+ const def = args[0];
538
+ return {
539
+ ...def,
540
+ noRuntimeInput: true
541
+ };
542
+ }
543
+ return defineAction;
544
+ }
545
+ var defineAction = makeDefineAction();
546
+
547
+ // src/api-wrapper/redaction.ts
548
+ var SENSITIVE_HEADER_KEYS = new Set([
549
+ "authorization",
550
+ "cookie",
551
+ "set-cookie",
552
+ "x-api-key",
553
+ "x-auth-token"
554
+ ]);
555
+ var SENSITIVE_BODY_KEYS = new Set([
556
+ "password",
557
+ "token",
558
+ "accesstoken",
559
+ "access_token",
560
+ "refreshtoken",
561
+ "refresh_token",
562
+ "secret",
563
+ "client_secret",
564
+ "clientsecret",
565
+ "apikey",
566
+ "api_key"
567
+ ]);
568
+ function redactHeaders(headers) {
569
+ if (!headers)
570
+ return;
571
+ const out = {};
572
+ for (const [k, v] of Object.entries(headers)) {
573
+ if (SENSITIVE_HEADER_KEYS.has(k.toLowerCase())) {
574
+ out[k] = "[REDACTED]";
575
+ } else {
576
+ out[k] = v;
577
+ }
578
+ }
579
+ return out;
580
+ }
581
+ function redactBody(body) {
582
+ if (body === null || body === undefined)
583
+ return body;
584
+ if (typeof body !== "object" || Array.isArray(body)) {
585
+ return body;
586
+ }
587
+ const rec = body;
588
+ const out = { ...rec };
589
+ for (const key of Object.keys(out)) {
590
+ if (SENSITIVE_BODY_KEYS.has(key.toLowerCase())) {
591
+ out[key] = "[REDACTED]";
592
+ }
593
+ }
594
+ return out;
595
+ }
596
+ function defaultRedactContext(ctx) {
597
+ return {
598
+ ...ctx,
599
+ requestHeaders: redactHeaders(ctx.requestHeaders),
600
+ requestBody: redactBody(ctx.requestBody),
601
+ meta: ctx.meta
602
+ };
603
+ }
604
+
605
+ // src/api-wrapper/error-reporting.ts
606
+ function applyErrorContextRedaction(ctx, config) {
607
+ const base = defaultRedactContext(ctx);
608
+ return config.redact ? config.redact(base) : base;
609
+ }
610
+ async function reportApiError(err, config) {
611
+ err.context = applyErrorContextRedaction(err.context, config);
612
+ try {
613
+ await config.onError?.(err);
614
+ } catch {}
615
+ config.logger?.error?.(err);
616
+ return err;
617
+ }
618
+
619
+ // src/api-wrapper/size.ts
620
+ function calculateSizeInMb(data) {
621
+ let bytes;
622
+ if (data === undefined || data === null) {
623
+ bytes = 0;
624
+ } else if (data instanceof Blob) {
625
+ bytes = data.size;
626
+ } else if (typeof FormData !== "undefined" && data instanceof FormData) {
627
+ bytes = 0;
628
+ data.forEach((value) => {
629
+ if (value instanceof Blob)
630
+ bytes += value.size;
631
+ else
632
+ bytes += new TextEncoder().encode(String(value)).length;
633
+ });
634
+ } else if (typeof data === "string") {
635
+ bytes = new TextEncoder().encode(data).length;
636
+ } else {
637
+ bytes = new TextEncoder().encode(JSON.stringify(data) ?? "").length;
638
+ }
639
+ return bytes / (1024 * 1024);
640
+ }
641
+
642
+ // src/api-wrapper/transport.ts
643
+ import { ZodError as ZodError3 } from "zod";
644
+ async function validateResponse(schema, data, ctx) {
87
645
  try {
88
646
  return await schema.parseAsync(data);
89
647
  } catch (error) {
90
- if (error instanceof ZodError) {
91
- throw new ResponseValidationError(error, data);
648
+ if (error instanceof ZodError3) {
649
+ throw new ResponseValidationError(error, data, ctx());
92
650
  }
93
651
  throw error;
94
652
  }
95
653
  }
96
- async function httpRequest(config) {
97
- const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
98
- const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
99
- const abortController = new AbortController;
100
- const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => abortController.abort(), config.timeoutMs);
654
+ async function httpRequest(config, ctx) {
655
+ const timeoutController = new AbortController;
656
+ const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
657
+ const signal = mergeSignals(timeoutController.signal, config.userSignal);
101
658
  try {
102
- return await config.fetchFn(fullUrl, {
659
+ return await config.fetchFn(config.fullUrl, {
103
660
  method: config.method,
104
661
  headers: config.headers,
105
- body: buildFetchBody(config.body, config.headers),
106
- signal: abortController.signal
662
+ body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError(msg, ctx())),
663
+ signal
107
664
  });
665
+ } catch (error) {
666
+ if (isDomAbortError(error)) {
667
+ if (config.userSignal?.aborted) {
668
+ throw new RequestAbortedError(ctx(), error);
669
+ }
670
+ if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
671
+ throw new RequestTimeoutError(config.timeoutMs, ctx());
672
+ }
673
+ throw new RequestAbortedError(ctx(), error);
674
+ }
675
+ if (error instanceof ConfigError)
676
+ throw error;
677
+ throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
108
678
  } finally {
109
679
  if (timeout)
110
680
  clearTimeout(timeout);
111
681
  }
112
682
  }
113
- async function makeRequestWithRetry(config, maxRetries, retryDelayMs, validateFn, shouldRetry, logger) {
683
+ function isDomAbortError(error) {
684
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
685
+ }
686
+ function mergeSignals(a, b) {
687
+ if (!b)
688
+ return a;
689
+ const anyFn = AbortSignal.any;
690
+ if (typeof anyFn === "function")
691
+ return anyFn([a, b]);
692
+ const controller = new AbortController;
693
+ const onAbort = () => controller.abort();
694
+ if (a.aborted)
695
+ controller.abort();
696
+ else
697
+ a.addEventListener("abort", onAbort, { once: true });
698
+ if (b.aborted)
699
+ controller.abort();
700
+ else
701
+ b.addEventListener("abort", onAbort, { once: true });
702
+ return controller.signal;
703
+ }
704
+ async function readErrorResponse(response) {
705
+ const body = await response.text();
706
+ return {
707
+ body,
708
+ headers: getHeadersAsObject(response.headers),
709
+ status: response.status
710
+ };
711
+ }
712
+ function getRetryDelay(params) {
713
+ const maxDelay = params.maxRetryDelayMs ?? 30000;
714
+ const { retryDelay, attempt, response } = params;
715
+ if (response && (response.status === 429 || response.status === 503)) {
716
+ const retryAfter = response.headers.get("retry-after");
717
+ if (retryAfter) {
718
+ const seconds = Number(retryAfter);
719
+ if (Number.isFinite(seconds)) {
720
+ return Math.min(Math.max(0, seconds * 1000), maxDelay);
721
+ }
722
+ const dateMs = Date.parse(retryAfter);
723
+ if (!Number.isNaN(dateMs)) {
724
+ return Math.min(Math.max(0, dateMs - Date.now()), maxDelay);
725
+ }
726
+ }
727
+ }
728
+ const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
729
+ return Math.min(delayMs, maxDelay);
730
+ }
731
+ function shouldAttemptRetryOnError(error) {
732
+ return !isNonRetryableApiError(error);
733
+ }
734
+ function shouldAttemptRetryOnHttp(retryCtx) {
735
+ const { error, response } = retryCtx;
736
+ if (error)
737
+ return shouldAttemptRetryOnError(error);
738
+ return response ? response.status >= 500 || response.status === 429 : false;
739
+ }
740
+ async function makeRequestWithRetry(args) {
741
+ const {
742
+ config,
743
+ maxRetries,
744
+ retryDelay,
745
+ maxRetryDelayMs,
746
+ validateFn,
747
+ shouldRetry,
748
+ logger,
749
+ ctx,
750
+ endpointName,
751
+ method,
752
+ fullUrl,
753
+ meta,
754
+ onResponse,
755
+ onRetry
756
+ } = args;
114
757
  let retryCount = 0;
115
758
  while (true) {
759
+ let response;
116
760
  try {
117
761
  logger?.debug?.("API request", config);
118
- const response = await httpRequest(config);
762
+ response = await httpRequest(config, (over) => ctx({ attempt: retryCount, ...over }));
763
+ await onResponse?.({
764
+ endpoint: endpointName,
765
+ method,
766
+ url: fullUrl,
767
+ response: response.clone(),
768
+ attempt: retryCount,
769
+ meta
770
+ });
119
771
  if (response.status >= 400) {
120
- if (retryCount < maxRetries && shouldRetry({ response, retryCount, maxRetries })) {
772
+ const retryCtx = {
773
+ response,
774
+ retryCount,
775
+ maxRetries,
776
+ endpoint: endpointName,
777
+ method,
778
+ url: fullUrl
779
+ };
780
+ if (retryCount < maxRetries && shouldRetry(retryCtx)) {
781
+ const delayMs = getRetryDelay({
782
+ retryDelay,
783
+ attempt: retryCount,
784
+ response,
785
+ maxRetryDelayMs
786
+ });
787
+ await onRetry?.({
788
+ ...retryCtx,
789
+ delayMs,
790
+ nextAttempt: retryCount + 1
791
+ });
121
792
  retryCount++;
122
- await delay(retryDelayMs);
793
+ await delay(delayMs);
123
794
  continue;
124
795
  }
125
- throw new HttpResponseError(response.status, await response.text());
796
+ const { body, headers: responseHeaders } = await readErrorResponse(response);
797
+ throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
126
798
  }
127
799
  return {
128
- validatedData: await validateFn(response),
800
+ validatedData: await validateFn(response, retryCount),
129
801
  retryCount,
130
802
  httpStatus: response.status
131
803
  };
132
804
  } catch (error) {
133
- if (error instanceof HttpResponseError)
805
+ if (isNonRetryableApiError(error)) {
134
806
  throw error;
135
- if (retryCount >= maxRetries || !shouldRetry({ error, retryCount, maxRetries })) {
807
+ }
808
+ const retryCtx = {
809
+ error,
810
+ retryCount,
811
+ maxRetries,
812
+ endpoint: endpointName,
813
+ method,
814
+ url: fullUrl
815
+ };
816
+ if (retryCount >= maxRetries || !shouldRetry(retryCtx)) {
136
817
  throw error;
137
818
  }
819
+ const delayMs = getRetryDelay({
820
+ retryDelay,
821
+ attempt: retryCount,
822
+ response: undefined,
823
+ maxRetryDelayMs
824
+ });
825
+ await onRetry?.({
826
+ ...retryCtx,
827
+ delayMs,
828
+ nextAttempt: retryCount + 1
829
+ });
138
830
  retryCount++;
139
- await delay(retryDelayMs);
831
+ await delay(delayMs);
140
832
  }
141
833
  }
142
834
  }
143
- async function validateAndTransformResponse(endpoint, response) {
835
+ async function validateAndTransformResponse(endpoint, response, ctx) {
144
836
  const responseHeaders = getHeadersAsObject(response.headers);
145
- const contentType = responseHeaders["content-type"] ?? "";
146
- const responseData = contentType.includes("application/json") ? await response.json() : await response.text();
837
+ const text = await response.text();
838
+ const responseSizeBytes = new TextEncoder().encode(text).length;
839
+ let responseData;
840
+ if (hasJsonContentType(responseHeaders)) {
841
+ try {
842
+ responseData = text === "" ? undefined : JSON.parse(text);
843
+ } catch (error) {
844
+ throw new ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
845
+ }
846
+ } else {
847
+ responseData = text;
848
+ }
147
849
  const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
148
- return validateResponse(endpoint.resSchema, transformedData);
850
+ const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
851
+ return { data, responseSizeBytes };
149
852
  }
150
- function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url) {
151
- if (url) {
152
- const finalUrl2 = replacePathParams(url, params);
153
- const queryString2 = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
154
- return {
155
- finalUrl: finalUrl2,
156
- fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
157
- };
853
+ function defaultShouldRetry(ctx) {
854
+ if (ctx.error)
855
+ return shouldAttemptRetryOnError(ctx.error);
856
+ return shouldAttemptRetryOnHttp(ctx);
857
+ }
858
+ function delay(ms) {
859
+ return new Promise((resolve) => setTimeout(resolve, ms));
860
+ }
861
+
862
+ // src/api-wrapper/create-client.ts
863
+ function isActionInvokeOptionsOnly(value) {
864
+ if (value === null || value === undefined)
865
+ return false;
866
+ if (typeof value !== "object" || Array.isArray(value))
867
+ return false;
868
+ return Object.keys(value).every((k) => k === "signal" || k === "meta");
869
+ }
870
+ function resolveActionInvocation(action, actionName, first, second) {
871
+ if (!action.noRuntimeInput) {
872
+ return { input: first, options: second ?? {} };
158
873
  }
159
- const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
160
- if (!defaultBaseUrl) {
161
- throw new Error("At least one base API URL is required");
874
+ if (first === undefined || first === null) {
875
+ return { input: undefined, options: second ?? {} };
162
876
  }
163
- const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
164
- const configBaseUrl = baseApiUrls[baseUrlKey];
165
- if (!configBaseUrl) {
166
- throw new Error(`Unknown base API URL: ${baseUrlKey}`);
877
+ if (second !== undefined) {
878
+ return { input: undefined, options: second };
167
879
  }
168
- const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
169
- const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
170
- const finalUrl = replacePathParams(`${baseUrl}${path}`, params);
171
- const queryString = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
172
- return {
173
- finalUrl,
174
- fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
175
- };
176
- }
177
- function buildQueryParams(endpoint, query) {
178
- const defaultParams = endpoint.reqDefaultQueryParams ?? {};
179
- const parsedQuery = endpoint.reqQuerySchema ? endpoint.reqQuerySchema.parse(query) : query ?? {};
180
- const merged = { ...defaultParams, ...parsedQuery };
181
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
880
+ if (isActionInvokeOptionsOnly(first)) {
881
+ return { input: undefined, options: first };
882
+ }
883
+ throw new ConfigError(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
884
+ endpoint: actionName,
885
+ method: "GET",
886
+ url: "",
887
+ attempt: 0,
888
+ maxRetries: 0,
889
+ elapsedMs: 0
890
+ });
182
891
  }
183
- function buildRequestBody(endpoint, body) {
184
- if (!endpoint.reqBodySchema)
185
- return body;
186
- const parsedBody = endpoint.reqBodySchema.parse(body);
187
- if (endpoint.reqBodyFormat !== "form-data") {
188
- return parsedBody;
892
+ function createApiClientImpl(apiConfig) {
893
+ let actions = {};
894
+ const cache = createActionCache();
895
+ const invokeEndpoint = async (endpointKey, input = {}) => {
896
+ const endpoint = apiConfig.endpoints[endpointKey];
897
+ const rawInput = input;
898
+ const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
899
+ const startedAt = Date.now();
900
+ const maxRetries = input.retries ?? apiConfig.retries ?? 3;
901
+ const baseContext = {
902
+ endpoint: endpointName,
903
+ method: endpoint?.method ?? "GET",
904
+ url: "",
905
+ attempt: 0,
906
+ maxRetries,
907
+ elapsedMs: 0,
908
+ meta: input.meta
909
+ };
910
+ const elapsed = () => Date.now() - startedAt;
911
+ const ctx = (over = {}) => ({
912
+ ...baseContext,
913
+ elapsedMs: elapsed(),
914
+ ...over
915
+ });
916
+ if (!endpoint) {
917
+ throw await reportApiError(new ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
918
+ }
919
+ baseContext.method = endpoint.method;
920
+ let pathParams;
921
+ let queryParams;
922
+ let body;
923
+ let headers;
924
+ try {
925
+ pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
926
+ queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
927
+ body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
928
+ headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
929
+ } catch (e) {
930
+ if (e instanceof ApiError)
931
+ throw await reportApiError(e, apiConfig);
932
+ throw e;
933
+ }
934
+ let fullUrl;
935
+ try {
936
+ const built = buildUrl({
937
+ endpoint,
938
+ params: pathParams,
939
+ queryParams,
940
+ baseApiUrls: apiConfig.baseApiUrls,
941
+ defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
942
+ url: input.url,
943
+ doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
944
+ configError: (msg) => new ConfigError(msg, ctx())
945
+ });
946
+ fullUrl = built.fullUrl;
947
+ } catch (e) {
948
+ if (e instanceof ApiError)
949
+ throw await reportApiError(e, apiConfig);
950
+ throw e;
951
+ }
952
+ baseContext.url = fullUrl;
953
+ baseContext.requestHeaders = headers;
954
+ baseContext.requestBody = body;
955
+ if (apiConfig.onRequest) {
956
+ try {
957
+ const result = await apiConfig.onRequest({
958
+ endpoint: endpointName,
959
+ method: endpoint.method,
960
+ url: fullUrl,
961
+ headers,
962
+ body,
963
+ meta: input.meta
964
+ });
965
+ if (result?.headers)
966
+ headers = result.headers;
967
+ if (result && "body" in result)
968
+ body = result.body;
969
+ } catch (e) {
970
+ if (e instanceof ApiError)
971
+ throw await reportApiError(e, apiConfig);
972
+ throw e;
973
+ }
974
+ }
975
+ const config = {
976
+ method: endpoint.method,
977
+ fullUrl,
978
+ body,
979
+ headers,
980
+ timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
981
+ fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
982
+ userSignal: input.signal
983
+ };
984
+ try {
985
+ const result = await makeRequestWithRetry({
986
+ config,
987
+ maxRetries,
988
+ retryDelay: apiConfig.retryDelayMs ?? 300,
989
+ maxRetryDelayMs: apiConfig.maxRetryDelayMs,
990
+ validateFn: (response, attempt) => validateAndTransformResponse(endpoint, response, (over) => ctx({ attempt, ...over })),
991
+ shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
992
+ logger: apiConfig.logger,
993
+ ctx,
994
+ endpointName,
995
+ method: endpoint.method,
996
+ fullUrl,
997
+ meta: input.meta,
998
+ onResponse: apiConfig.onResponse,
999
+ onRetry: apiConfig.onRetry
1000
+ });
1001
+ const responseTimeMs = elapsed();
1002
+ const responseSizeMb = result.validatedData.responseSizeBytes / (1024 * 1024);
1003
+ const requestBodySizeMb = calculateSizeInMb(config.body);
1004
+ return {
1005
+ data: result.validatedData.data,
1006
+ requestBodySizeMb,
1007
+ responseSizeMb,
1008
+ responseTimeMs,
1009
+ httpStatus: result.httpStatus,
1010
+ retryCount: result.retryCount
1011
+ };
1012
+ } catch (error) {
1013
+ if (error instanceof ApiError)
1014
+ throw await reportApiError(error, apiConfig);
1015
+ throw error;
1016
+ }
1017
+ };
1018
+ async function invokeAction(name, rawInput, options = {}) {
1019
+ const action = actions[name];
1020
+ const inputForHandler = action.noRuntimeInput ? undefined : rawInput;
1021
+ const actionName = apiConfig.name ? `${apiConfig.name}.${name}` : name;
1022
+ const startedAt = Date.now();
1023
+ const baseContext = {
1024
+ endpoint: actionName,
1025
+ method: "GET",
1026
+ url: "",
1027
+ attempt: 0,
1028
+ maxRetries: 0,
1029
+ elapsedMs: 0,
1030
+ meta: options.meta,
1031
+ requestBody: action.noRuntimeInput ? undefined : rawInput
1032
+ };
1033
+ const ctx = (over = {}) => ({
1034
+ ...baseContext,
1035
+ elapsedMs: Date.now() - startedAt,
1036
+ ...over
1037
+ });
1038
+ apiConfig.logger?.debug?.("Action start", { action: actionName });
1039
+ try {
1040
+ const result = await action.handler({
1041
+ input: inputForHandler,
1042
+ api: proxy,
1043
+ signal: options.signal,
1044
+ meta: options.meta,
1045
+ logger: apiConfig.logger,
1046
+ cache
1047
+ });
1048
+ apiConfig.logger?.debug?.("Action end", {
1049
+ action: actionName,
1050
+ elapsedMs: Date.now() - startedAt
1051
+ });
1052
+ return result;
1053
+ } catch (error) {
1054
+ if (error instanceof ApiError) {
1055
+ throw await reportApiError(error, apiConfig);
1056
+ }
1057
+ const wrapped = new ActionError(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
1058
+ throw await reportApiError(wrapped, apiConfig);
1059
+ }
189
1060
  }
190
- const formData = new FormData;
191
- for (const [key, value] of Object.entries(parsedBody)) {
192
- if (value == null)
193
- continue;
194
- formData.set(key, value instanceof Blob ? value : String(value));
1061
+ async function dispatchAction(name, first, second) {
1062
+ const action = actions[name];
1063
+ const { input, options } = resolveActionInvocation(action, name, first, second);
1064
+ return invokeAction(name, input, options);
195
1065
  }
196
- return formData;
197
- }
198
- function buildHeaders(endpoint, headers, defaultHeaders) {
199
- const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
200
- const parsedHeaders = endpoint.reqHeadersSchema ? endpoint.reqHeadersSchema.parse(headers) : headers ?? {};
201
- return {
202
- ...defaultHeaders,
203
- ...contentType && { "Content-Type": contentType },
204
- ...endpoint.defaultHeaders,
205
- ...parsedHeaders
1066
+ const request = async (key, input, options) => {
1067
+ if (key in actions) {
1068
+ return dispatchAction(key, input, options);
1069
+ }
1070
+ return invokeEndpoint(key, input ?? {});
206
1071
  };
207
- }
208
- function buildFetchBody(body, headers) {
209
- if (body === undefined || body === null)
210
- return;
211
- if (body instanceof FormData || body instanceof Blob)
212
- return body;
213
- if (headers["Content-Type"] === "application/json")
214
- return JSON.stringify(body);
215
- return body;
216
- }
217
- function replacePathParams(path, params) {
218
- return path.replace(/:(\w+)/g, (_, key) => {
219
- const value = params?.[key];
220
- if (value === undefined) {
221
- throw new Error(`Missing required path parameter: ${key}`);
1072
+ const client = { request, cache };
1073
+ const proxy = new Proxy(client, {
1074
+ get(target, prop, receiver) {
1075
+ if (prop in target || typeof prop !== "string") {
1076
+ return Reflect.get(target, prop, receiver);
1077
+ }
1078
+ if (prop in apiConfig.endpoints) {
1079
+ return (input) => invokeEndpoint(prop, input ?? {});
1080
+ }
1081
+ if (prop in actions) {
1082
+ return (first, second) => dispatchAction(prop, first, second);
1083
+ }
1084
+ return;
222
1085
  }
223
- return encodeURIComponent(String(value));
224
1086
  });
1087
+ const rawActions = apiConfig.actions;
1088
+ if (typeof rawActions === "function") {
1089
+ actions = rawActions({
1090
+ api: proxy,
1091
+ defineAction: makeDefineAction()
1092
+ });
1093
+ } else if (rawActions) {
1094
+ actions = rawActions;
1095
+ }
1096
+ const collisions = Object.keys(actions).filter((key) => (key in apiConfig.endpoints));
1097
+ if (collisions.length > 0) {
1098
+ throw new ConfigError(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
1099
+ endpoint: collisions[0],
1100
+ method: "GET",
1101
+ url: "",
1102
+ attempt: 0,
1103
+ maxRetries: 0,
1104
+ elapsedMs: 0
1105
+ });
1106
+ }
1107
+ return proxy;
225
1108
  }
226
- function getHeadersAsObject(headers) {
227
- const result = {};
228
- headers.forEach((value, key) => {
229
- result[key] = value;
230
- });
231
- return result;
232
- }
233
- function calculateSizeInMb(data) {
234
- const sizeInBytes = new TextEncoder().encode(JSON.stringify(data)).length;
235
- return sizeInBytes / (1024 * 1024);
236
- }
237
- function defaultShouldRetry({ error, response }) {
238
- if (error)
239
- return true;
240
- return response ? response.status >= 500 : false;
241
- }
242
- function delay(ms) {
243
- return new Promise((resolve) => setTimeout(resolve, ms));
244
- }
1109
+ var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
245
1110
  export {
246
- createApiWrapper,
247
- createApiClient,
1111
+ ActionError,
1112
+ ApiError,
1113
+ ConfigError,
1114
+ HttpResponseError,
1115
+ NetworkError,
1116
+ RequestAbortedError,
1117
+ RequestTimeoutError,
1118
+ RequestValidationError,
248
1119
  ResponseValidationError,
249
- HttpResponseError
1120
+ createApiClient,
1121
+ defineAction,
1122
+ isActionError,
1123
+ isApiError,
1124
+ isConfigError,
1125
+ isHttpResponseError,
1126
+ isNetworkError,
1127
+ isNonRetryableApiError,
1128
+ isRequestAbortedError,
1129
+ isRequestTimeoutError,
1130
+ isRequestValidationError,
1131
+ isResponseValidationError
250
1132
  };