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