hanzio 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +786 -239
- package/dist/api-wrapper/builders.d.ts +4 -3
- package/dist/api-wrapper/create-client.d.ts +1 -1
- package/dist/api-wrapper/describe.d.ts +16 -0
- package/dist/api-wrapper/endpoint.d.ts +18 -0
- package/dist/api-wrapper/errors.d.ts +39 -2
- package/dist/api-wrapper/index.d.ts +5 -0
- package/dist/api-wrapper/otel.d.ts +44 -0
- package/dist/api-wrapper/paginate.d.ts +12 -0
- package/dist/api-wrapper/redaction.d.ts +2 -0
- package/dist/api-wrapper/shared.d.ts +16 -2
- package/dist/api-wrapper/testing.d.ts +23 -0
- package/dist/api-wrapper/transport.d.ts +4 -5
- package/dist/api-wrapper/types.d.ts +103 -18
- package/dist/array/index.d.ts +50 -4
- package/dist/cache/createCache.d.ts +31 -0
- package/dist/cache/index.d.ts +19 -2
- package/dist/color/contrast.d.ts +40 -0
- package/dist/color/index.d.ts +2 -0
- package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
- package/dist/cool-console-log/index.d.ts +34 -5
- package/dist/crypto/encoding.d.ts +18 -0
- package/dist/crypto/hash.d.ts +33 -0
- package/dist/crypto/index.d.ts +4 -0
- package/dist/crypto/sealer.d.ts +25 -0
- package/dist/crypto/webhook.d.ts +20 -0
- package/dist/date/index.d.ts +46 -2
- package/dist/emitter/index.d.ts +21 -0
- package/dist/error/index.d.ts +12 -0
- package/dist/format/index.d.ts +102 -0
- package/dist/function/index.d.ts +60 -0
- package/dist/guard/index.d.ts +20 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/index-0zvtcbzy.js +224 -0
- package/dist/index-181e6vfw.js +29 -0
- package/dist/index-8r2vdzjg.js +87 -0
- package/dist/index-hy7kbb99.js +157 -0
- package/dist/{src/secrets/infisical.js → index-j1ybktbr.js} +500 -538
- package/dist/index-k9yx6bne.js +89 -0
- package/dist/index-kj1phgrx.js +169 -0
- package/dist/index-mnkcfyx8.js +78 -0
- package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
- package/dist/index-rp5pqghz.js +157 -0
- package/dist/index-srt06xd2.js +15 -0
- package/dist/index-wjyb9bsd.js +263 -0
- package/dist/index.d.ts +7 -7
- package/dist/jwt/index.d.ts +50 -2
- package/dist/math/index.d.ts +2 -2
- package/dist/math/linearRegressionTrend.d.ts +14 -1
- package/dist/math/scale.d.ts +8 -0
- package/dist/object/index.d.ts +19 -0
- package/dist/p-queue/index.d.ts +1 -1
- package/dist/promise/index.d.ts +97 -2
- package/dist/sitemap/index.d.ts +48 -6
- package/dist/src/api-wrapper/index.js +60 -1130
- package/dist/src/api-wrapper/testing.js +20 -0
- package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
- package/dist/src/crypto/index.js +195 -0
- package/dist/src/format/index.js +211 -0
- package/dist/src/i18n/index.js +36 -0
- package/dist/src/index.js +739 -3145
- package/dist/src/jwt/index.js +138 -92
- package/dist/src/p-queue/index.js +9 -437
- package/dist/src/secrets/index.js +44 -1428
- package/dist/src/secrets/vite.js +6 -39
- package/dist/src/secrets/worker.js +10 -273
- package/dist/src/sitemap/index.js +183 -511
- package/dist/src/storage/index.js +119 -0
- package/dist/src/zod/index.js +340 -167
- package/dist/state/index.d.ts +21 -0
- package/dist/storage/index.d.ts +58 -0
- package/dist/string/index.d.ts +75 -2
- package/dist/typedswitch/index.d.ts +31 -24
- package/dist/types/index.d.ts +11 -0
- package/dist/url/index.d.ts +50 -1
- package/dist/zod/index.d.ts +32 -3
- package/dist/zod/introspect.d.ts +19 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
- package/dist/zod/zodMinMaxFilter.d.ts +22 -3
- package/dist/zod/zodToTypeString.d.ts +8 -3
- package/package.json +78 -7
- package/src/api-wrapper/actions-impl.ts +11 -72
- package/src/api-wrapper/builders.ts +32 -25
- package/src/api-wrapper/create-client.ts +171 -41
- package/src/api-wrapper/describe.ts +157 -0
- package/src/api-wrapper/endpoint.ts +26 -0
- package/src/api-wrapper/error-reporting.ts +7 -1
- package/src/api-wrapper/errors.ts +107 -2
- package/src/api-wrapper/headers.ts +11 -6
- package/src/api-wrapper/index.ts +5 -0
- package/src/api-wrapper/otel.ts +139 -0
- package/src/api-wrapper/paginate.ts +28 -0
- package/src/api-wrapper/redaction.ts +42 -0
- package/src/api-wrapper/shared.ts +21 -2
- package/src/api-wrapper/testing.ts +48 -0
- package/src/api-wrapper/transport.ts +41 -43
- package/src/api-wrapper/types.ts +185 -18
- package/src/array/index.ts +195 -42
- package/src/cache/createCache.ts +157 -0
- package/src/cache/index.ts +132 -147
- package/src/color/contrast.ts +163 -0
- package/src/color/index.ts +22 -0
- package/src/{math → color}/metricsSeriesColors.ts +47 -49
- package/src/cool-console-log/index.ts +128 -41
- package/src/crypto/encoding.ts +83 -0
- package/src/crypto/hash.ts +170 -0
- package/src/crypto/index.ts +26 -0
- package/src/crypto/sealer.ts +168 -0
- package/src/crypto/webhook.ts +86 -0
- package/src/date/index.ts +109 -34
- package/src/emitter/index.ts +82 -0
- package/src/error/index.ts +63 -0
- package/src/format/index.ts +389 -0
- package/src/function/index.ts +201 -0
- package/src/guard/index.ts +80 -0
- package/src/i18n/index.ts +125 -0
- package/src/index.ts +11 -7
- package/src/jwt/index.ts +199 -125
- package/src/math/index.ts +5 -10
- package/src/math/linearRegressionTrend.ts +33 -8
- package/src/math/scale.ts +17 -0
- package/src/object/index.ts +99 -0
- package/src/p-queue/index.ts +2 -0
- package/src/promise/index.ts +304 -9
- package/src/sitemap/index.ts +293 -130
- package/src/state/index.ts +133 -81
- package/src/storage/index.ts +187 -0
- package/src/string/index.ts +261 -23
- package/src/typedswitch/index.ts +58 -38
- package/src/types/index.ts +14 -0
- package/src/url/index.ts +146 -1
- package/src/zod/index.ts +105 -9
- package/src/zod/introspect.ts +103 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -21
- package/src/zod/zodMinMaxFilter.ts +37 -13
- package/src/zod/zodToTypeString.ts +184 -181
- package/dist/api-wrapper/index.js +0 -250
- package/dist/gzip/index.d.ts +0 -2
- package/dist/gzip/index.js +0 -16
- package/dist/index.js +0 -1823
- package/dist/jwt/index.js +0 -19
- package/dist/sitemap/index.js +0 -552
- package/dist/src/gzip/index.js +0 -16
- package/dist/src/secrets/loaders.js +0 -1529
- package/dist/zod/index.js +0 -210
|
@@ -1,1132 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
-
}
|
|
194
|
-
|
|
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 {
|
|
245
|
-
zodError;
|
|
246
|
-
rawResponse;
|
|
247
|
-
issues;
|
|
248
|
-
validationIssues;
|
|
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 });
|
|
254
|
-
this.name = "ResponseValidationError";
|
|
255
|
-
this.zodError = zodError;
|
|
256
|
-
this.rawResponse = rawResponse;
|
|
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
|
-
};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
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
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
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");
|
|
373
|
-
}
|
|
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) {
|
|
405
|
-
try {
|
|
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;
|
|
499
|
-
} catch (error) {
|
|
500
|
-
store.delete(key);
|
|
501
|
-
throw error;
|
|
502
|
-
}
|
|
503
|
-
};
|
|
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;
|
|
528
|
-
}
|
|
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) {
|
|
645
|
-
try {
|
|
646
|
-
return await schema.parseAsync(data);
|
|
647
|
-
} catch (error) {
|
|
648
|
-
if (error instanceof ZodError3) {
|
|
649
|
-
throw new ResponseValidationError(error, data, ctx());
|
|
650
|
-
}
|
|
651
|
-
throw error;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
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);
|
|
658
|
-
try {
|
|
659
|
-
return await config.fetchFn(config.fullUrl, {
|
|
660
|
-
method: config.method,
|
|
661
|
-
headers: config.headers,
|
|
662
|
-
body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError(msg, ctx())),
|
|
663
|
-
signal
|
|
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);
|
|
678
|
-
} finally {
|
|
679
|
-
if (timeout)
|
|
680
|
-
clearTimeout(timeout);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
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;
|
|
757
|
-
let retryCount = 0;
|
|
758
|
-
while (true) {
|
|
759
|
-
let response;
|
|
760
|
-
try {
|
|
761
|
-
logger?.debug?.("API request", 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
|
-
});
|
|
771
|
-
if (response.status >= 400) {
|
|
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
|
-
});
|
|
792
|
-
retryCount++;
|
|
793
|
-
await delay(delayMs);
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
const { body, headers: responseHeaders } = await readErrorResponse(response);
|
|
797
|
-
throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
|
|
798
|
-
}
|
|
799
|
-
return {
|
|
800
|
-
validatedData: await validateFn(response, retryCount),
|
|
801
|
-
retryCount,
|
|
802
|
-
httpStatus: response.status
|
|
803
|
-
};
|
|
804
|
-
} catch (error) {
|
|
805
|
-
if (isNonRetryableApiError(error)) {
|
|
806
|
-
throw error;
|
|
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)) {
|
|
817
|
-
throw error;
|
|
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
|
-
});
|
|
830
|
-
retryCount++;
|
|
831
|
-
await delay(delayMs);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
async function validateAndTransformResponse(endpoint, response, ctx) {
|
|
836
|
-
const responseHeaders = getHeadersAsObject(response.headers);
|
|
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
|
-
}
|
|
849
|
-
const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
|
|
850
|
-
const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
|
|
851
|
-
return { data, responseSizeBytes };
|
|
852
|
-
}
|
|
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 ?? {} };
|
|
873
|
-
}
|
|
874
|
-
if (first === undefined || first === null) {
|
|
875
|
-
return { input: undefined, options: second ?? {} };
|
|
876
|
-
}
|
|
877
|
-
if (second !== undefined) {
|
|
878
|
-
return { input: undefined, options: second };
|
|
879
|
-
}
|
|
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
|
-
});
|
|
891
|
-
}
|
|
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
|
-
}
|
|
1060
|
-
}
|
|
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);
|
|
1065
|
-
}
|
|
1066
|
-
const request = async (key, input, options) => {
|
|
1067
|
-
if (key in actions) {
|
|
1068
|
-
return dispatchAction(key, input, options);
|
|
1069
|
-
}
|
|
1070
|
-
return invokeEndpoint(key, input ?? {});
|
|
1071
|
-
};
|
|
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;
|
|
1085
|
-
}
|
|
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;
|
|
1108
|
-
}
|
|
1109
|
-
var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
|
|
1
|
+
import {
|
|
2
|
+
ApiError2,
|
|
3
|
+
HttpResponseError2,
|
|
4
|
+
ResponseValidationError2,
|
|
5
|
+
RequestValidationError2,
|
|
6
|
+
RequestTimeoutError2,
|
|
7
|
+
NetworkError2,
|
|
8
|
+
RequestAbortedError2,
|
|
9
|
+
ConfigError2,
|
|
10
|
+
ApiResponseError2,
|
|
11
|
+
ActionError2,
|
|
12
|
+
isApiError2,
|
|
13
|
+
isHttpResponseError2,
|
|
14
|
+
isResponseValidationError2,
|
|
15
|
+
isRequestValidationError2,
|
|
16
|
+
isRequestTimeoutError2,
|
|
17
|
+
isNetworkError2,
|
|
18
|
+
isRequestAbortedError2,
|
|
19
|
+
isConfigError2,
|
|
20
|
+
isActionError2,
|
|
21
|
+
isApiResponseError2,
|
|
22
|
+
isNonRetryableApiError2,
|
|
23
|
+
defineAction2,
|
|
24
|
+
describeApiError2,
|
|
25
|
+
apiErrorLogAttributes2,
|
|
26
|
+
defaultShouldRetry2,
|
|
27
|
+
createApiClient2,
|
|
28
|
+
paginate2,
|
|
29
|
+
otelMiddleware2,
|
|
30
|
+
defineEndpoint2
|
|
31
|
+
} from "../../index-j1ybktbr.js";
|
|
1110
32
|
export {
|
|
1111
|
-
ActionError,
|
|
1112
|
-
ApiError,
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
33
|
+
ActionError2 as ActionError,
|
|
34
|
+
ApiError2 as ApiError,
|
|
35
|
+
ApiResponseError2 as ApiResponseError,
|
|
36
|
+
ConfigError2 as ConfigError,
|
|
37
|
+
HttpResponseError2 as HttpResponseError,
|
|
38
|
+
NetworkError2 as NetworkError,
|
|
39
|
+
RequestAbortedError2 as RequestAbortedError,
|
|
40
|
+
RequestTimeoutError2 as RequestTimeoutError,
|
|
41
|
+
RequestValidationError2 as RequestValidationError,
|
|
42
|
+
ResponseValidationError2 as ResponseValidationError,
|
|
43
|
+
apiErrorLogAttributes2 as apiErrorLogAttributes,
|
|
44
|
+
createApiClient2 as createApiClient,
|
|
45
|
+
defaultShouldRetry2 as defaultShouldRetry,
|
|
46
|
+
defineAction2 as defineAction,
|
|
47
|
+
defineEndpoint2 as defineEndpoint,
|
|
48
|
+
describeApiError2 as describeApiError,
|
|
49
|
+
isActionError2 as isActionError,
|
|
50
|
+
isApiError2 as isApiError,
|
|
51
|
+
isApiResponseError2 as isApiResponseError,
|
|
52
|
+
isConfigError2 as isConfigError,
|
|
53
|
+
isHttpResponseError2 as isHttpResponseError,
|
|
54
|
+
isNetworkError2 as isNetworkError,
|
|
55
|
+
isNonRetryableApiError2 as isNonRetryableApiError,
|
|
56
|
+
isRequestAbortedError2 as isRequestAbortedError,
|
|
57
|
+
isRequestTimeoutError2 as isRequestTimeoutError,
|
|
58
|
+
isRequestValidationError2 as isRequestValidationError,
|
|
59
|
+
isResponseValidationError2 as isResponseValidationError,
|
|
60
|
+
otelMiddleware2 as otelMiddleware,
|
|
61
|
+
paginate2 as paginate
|
|
1132
62
|
};
|