hanzio 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +70 -4
  2. package/dist/api-wrapper/builders.d.ts +13 -2
  3. package/dist/api-wrapper/errors.d.ts +1 -0
  4. package/dist/api-wrapper/headers.d.ts +2 -1
  5. package/dist/api-wrapper/transport.d.ts +8 -5
  6. package/dist/api-wrapper/types.d.ts +1 -0
  7. package/dist/cache/index.d.ts +26 -0
  8. package/dist/secrets/core.d.ts +47 -0
  9. package/dist/secrets/deadline.d.ts +6 -0
  10. package/dist/secrets/environment.d.ts +9 -0
  11. package/dist/secrets/errors.d.ts +11 -0
  12. package/dist/secrets/index.d.ts +13 -25
  13. package/dist/secrets/infisical-cli.d.ts +6 -0
  14. package/dist/secrets/infisical-config.d.ts +17 -0
  15. package/dist/secrets/infisical-session.d.ts +4 -0
  16. package/dist/secrets/infisical.d.ts +8 -13
  17. package/dist/secrets/loaders.d.ts +5 -12
  18. package/dist/secrets/vite.d.ts +7 -3
  19. package/dist/secrets/worker-loader.d.ts +9 -0
  20. package/dist/secrets/worker.d.ts +3 -0
  21. package/dist/src/api-wrapper/index.js +152 -122
  22. package/dist/src/index.js +3173 -2713
  23. package/dist/src/jwt/index.js +2 -2
  24. package/dist/src/p-queue/index.js +3 -3
  25. package/dist/src/secrets/index.js +670 -402
  26. package/dist/src/secrets/infisical.js +451 -168
  27. package/dist/src/secrets/loaders.js +467 -174
  28. package/dist/src/secrets/vite.js +33 -11
  29. package/dist/src/secrets/worker.js +275 -0
  30. package/dist/src/sitemap/index.js +1 -1
  31. package/dist/src/zod/index.js +7 -7
  32. package/dist/typedswitch/index.d.ts +9 -5
  33. package/package.json +12 -3
  34. package/src/api-wrapper/builders.ts +68 -43
  35. package/src/api-wrapper/create-client.ts +82 -62
  36. package/src/api-wrapper/errors.ts +10 -0
  37. package/src/api-wrapper/headers.ts +14 -1
  38. package/src/api-wrapper/redaction.ts +1 -0
  39. package/src/api-wrapper/transport.ts +34 -38
  40. package/src/api-wrapper/types.ts +1 -0
  41. package/src/cache/index.ts +228 -61
  42. package/src/secrets/core.ts +348 -0
  43. package/src/secrets/deadline.ts +69 -0
  44. package/src/secrets/environment.ts +69 -0
  45. package/src/secrets/errors.ts +29 -0
  46. package/src/secrets/index.ts +79 -210
  47. package/src/secrets/infisical-cli.ts +99 -0
  48. package/src/secrets/infisical-config.ts +70 -0
  49. package/src/secrets/infisical-session.ts +42 -0
  50. package/src/secrets/infisical.ts +214 -66
  51. package/src/secrets/loaders.ts +24 -48
  52. package/src/secrets/vite.ts +44 -15
  53. package/src/secrets/worker-loader.ts +56 -0
  54. package/src/secrets/worker.ts +4 -0
  55. package/src/typedswitch/index.ts +80 -69
@@ -1,3 +1,247 @@
1
+ // src/secrets/errors.ts
2
+ class SecretLoadError extends Error {
3
+ code;
4
+ operation;
5
+ status;
6
+ constructor(code, message, details = {}) {
7
+ super(message);
8
+ this.name = "SecretLoadError";
9
+ this.code = code;
10
+ this.operation = details.operation;
11
+ this.status = details.status;
12
+ }
13
+ }
14
+
15
+ // src/secrets/deadline.ts
16
+ async function withSecretDeadline(options, operation) {
17
+ const timeoutMs = options.timeoutMs ?? 30000;
18
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
19
+ throw new SecretLoadError("CONFIGURATION", "Secret timeoutMs must be a positive finite timer duration.");
20
+ }
21
+ const controller = new AbortController;
22
+ const cancel = () => controller.abort(new SecretLoadError("ABORTED", "Secret loading was cancelled."));
23
+ if (options.signal?.aborted) {
24
+ cancel();
25
+ } else {
26
+ options.signal?.addEventListener("abort", cancel, { once: true });
27
+ }
28
+ const timer = setTimeout(() => controller.abort(new SecretLoadError("TIMEOUT", `Secret loading timed out after ${timeoutMs}ms.`)), timeoutMs);
29
+ let rejectOnAbort = () => {};
30
+ try {
31
+ const aborted = new Promise((_, reject) => {
32
+ rejectOnAbort = () => reject(controller.signal.reason);
33
+ controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
34
+ });
35
+ if (controller.signal.aborted) {
36
+ throw controller.signal.reason;
37
+ }
38
+ return await Promise.race([operation(controller.signal), aborted]);
39
+ } finally {
40
+ clearTimeout(timer);
41
+ options.signal?.removeEventListener("abort", cancel);
42
+ controller.signal.removeEventListener("abort", rejectOnAbort);
43
+ }
44
+ }
45
+
46
+ // src/secrets/environment.ts
47
+ function getProcessEnvironment() {
48
+ return typeof process === "undefined" ? undefined : process.env;
49
+ }
50
+ function resolveSecretEnvironment(option, env) {
51
+ const explicit = typeof option === "function" ? option() : option;
52
+ if (typeof explicit === "object" && explicit !== null) {
53
+ if (typeof explicit.slug !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(explicit.slug)) {
54
+ throw new SecretLoadError("CONFIGURATION", "Custom secret environment requires a valid slug.");
55
+ }
56
+ return explicit.slug;
57
+ }
58
+ const selected = explicit ?? env?.SECRETS_ENV;
59
+ if (selected !== undefined) {
60
+ if (selected === "production" || selected === "prod") {
61
+ return "prod";
62
+ }
63
+ if (selected === "development" || selected === "dev") {
64
+ return "dev";
65
+ }
66
+ if (selected === "staging") {
67
+ return "staging";
68
+ }
69
+ throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
70
+ }
71
+ if (env?.NODE_ENV === "production" || env?.NODE_ENV === "prod") {
72
+ return "prod";
73
+ }
74
+ if (env?.NODE_ENV === "staging") {
75
+ return "staging";
76
+ }
77
+ return "dev";
78
+ }
79
+
80
+ // src/secrets/core.ts
81
+ var registryKey = Symbol.for("hanzio.secrets.environmentWrites.v1");
82
+ var shared = globalThis;
83
+ var environmentWrites = shared[registryKey] ?? new WeakMap;
84
+ shared[registryKey] = environmentWrites;
85
+ var defaultLogger = {
86
+ info: (message, attrs) => console.log(`${message} environment=${attrs.environment} count=${attrs.count} source=${attrs.source} durationMs=${attrs.durationMs}`, { sources: attrs.sources }),
87
+ error: (message, attrs) => console.error(message, attrs)
88
+ };
89
+ function readOverrides(keys, env) {
90
+ if (!env) {
91
+ return {};
92
+ }
93
+ const writes = environmentWrites.get(env);
94
+ return Object.fromEntries(keys.flatMap((key) => {
95
+ const value = Object.hasOwn(env, key) ? env[key] : undefined;
96
+ if (writes?.has(key)) {
97
+ if (writes.get(key) === value) {
98
+ return [];
99
+ }
100
+ writes.delete(key);
101
+ }
102
+ return value ? [[key, value]] : [];
103
+ }));
104
+ }
105
+ async function defineSecretSet(keys, options, environmentVariables) {
106
+ const keySet = [...new Set(keys)];
107
+ const environment = resolveSecretEnvironment(options.environment, environmentVariables);
108
+ const logger = options.logger === false ? undefined : options.logger ?? defaultLogger;
109
+ let cached;
110
+ let parsed;
111
+ let sources;
112
+ let inFlight;
113
+ const log = (level, message, attributes) => {
114
+ try {
115
+ logger?.[level](message, attributes);
116
+ } catch {}
117
+ };
118
+ const loadSnapshot = async (request, signal) => {
119
+ const local = readOverrides(keySet, environmentVariables);
120
+ const missing = keySet.filter((key) => !Object.hasOwn(local, key));
121
+ let loaderSource = options.loader.source ?? "custom loader";
122
+ let loaded = {};
123
+ if (missing.length) {
124
+ try {
125
+ loaded = await options.loader({
126
+ keys: missing,
127
+ environment,
128
+ signal,
129
+ timeoutMs: request.timeoutMs ?? options.timeoutMs ?? 30000,
130
+ reportSource: (source) => {
131
+ loaderSource = source;
132
+ }
133
+ });
134
+ } catch (error) {
135
+ if (error instanceof SecretLoadError) {
136
+ throw error;
137
+ }
138
+ throw new SecretLoadError("LOADER", "Secret loader failed.");
139
+ }
140
+ }
141
+ const overrides = readOverrides(keySet, environmentVariables);
142
+ const next = Object.fromEntries(keySet.map((key) => {
143
+ if (Object.hasOwn(overrides, key)) {
144
+ return [key, overrides[key]];
145
+ }
146
+ if (Object.hasOwn(loaded, key)) {
147
+ return [key, loaded[key]];
148
+ }
149
+ return [key, undefined];
150
+ }));
151
+ const missingKeys = keySet.filter((key) => typeof next[key] !== "string" || !next[key]);
152
+ if (missingKeys.length) {
153
+ throw new SecretLoadError("MISSING_SECRETS", `Missing secrets: ${missingKeys.join(", ")}`);
154
+ }
155
+ let nextParsed;
156
+ try {
157
+ nextParsed = options.schema ? await options.schema.parseAsync({ ...next }) : { ...next };
158
+ nextParsed = structuredClone(nextParsed);
159
+ } catch {
160
+ throw new SecretLoadError("VALIDATION", "Secret schema validation failed. Check the configured schema; values are omitted.");
161
+ }
162
+ signal.throwIfAborted();
163
+ const nextSources = Object.fromEntries(keySet.map((key) => [
164
+ key,
165
+ Object.hasOwn(overrides, key) ? "process.env" : loaderSource
166
+ ]));
167
+ if (options.writeToProcessEnv !== false && environmentVariables) {
168
+ let writes = environmentWrites.get(environmentVariables);
169
+ if (!writes) {
170
+ writes = new Map;
171
+ environmentWrites.set(environmentVariables, writes);
172
+ }
173
+ for (const key of keySet) {
174
+ if (Object.hasOwn(overrides, key)) {
175
+ continue;
176
+ }
177
+ environmentVariables[key] = next[key];
178
+ writes.set(key, next[key]);
179
+ }
180
+ }
181
+ cached = next;
182
+ parsed = nextParsed;
183
+ sources = nextSources;
184
+ };
185
+ const load = (request = {}) => {
186
+ if (inFlight) {
187
+ return inFlight;
188
+ }
189
+ inFlight = (async () => {
190
+ const start = Date.now();
191
+ try {
192
+ await withSecretDeadline({
193
+ timeoutMs: request.timeoutMs ?? options.timeoutMs,
194
+ signal: request.signal ?? options.signal
195
+ }, (signal) => loadSnapshot(request, signal));
196
+ const sourceCounts = new Map;
197
+ for (const key of keySet) {
198
+ const source = sources[key];
199
+ sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
200
+ }
201
+ const counts = Object.fromEntries(sourceCounts);
202
+ log("info", "Loaded secrets", {
203
+ environment,
204
+ count: keySet.length,
205
+ durationMs: Date.now() - start,
206
+ source: Object.keys(counts).join(" + ") || "none",
207
+ sources: counts
208
+ });
209
+ } catch (error) {
210
+ const safe = error instanceof SecretLoadError ? error : new SecretLoadError("LOADER", "Secret loading failed.");
211
+ log("error", "Secret loading failed", {
212
+ environment,
213
+ count: keySet.length,
214
+ durationMs: Date.now() - start,
215
+ code: safe.code
216
+ });
217
+ throw safe;
218
+ }
219
+ })().finally(() => {
220
+ inFlight = undefined;
221
+ });
222
+ return inFlight;
223
+ };
224
+ const secretSet = {
225
+ secret(key) {
226
+ if (!Object.hasOwn(cached, key)) {
227
+ throw new SecretLoadError("MISSING_SECRETS", `Secret ${key} not found`);
228
+ }
229
+ return cached[key];
230
+ },
231
+ secrets: () => ({ ...cached }),
232
+ parsed: () => structuredClone(parsed),
233
+ sources: () => ({ ...sources }),
234
+ async reload(request) {
235
+ await load(request);
236
+ return secretSet;
237
+ }
238
+ };
239
+ await load();
240
+ return secretSet;
241
+ }
242
+
243
+ // src/secrets/infisical.ts
244
+ import { z as z2 } from "zod";
1
245
  // src/api-wrapper/headers.ts
2
246
  var CANONICAL_HEADER_NAMES = {
3
247
  "content-type": "Content-Type",
@@ -38,13 +282,16 @@ function hasJsonContentType(headers) {
38
282
  const ct = getHeader(headers, "content-type") ?? "";
39
283
  return ct.includes("application/json");
40
284
  }
41
- function buildFetchBody(body, headers) {
285
+ function buildFetchBody(body, headers, configError) {
42
286
  if (body === undefined || body === null)
43
287
  return;
44
288
  if (body instanceof FormData || body instanceof Blob)
45
289
  return body;
46
290
  if (hasJsonContentType(headers))
47
291
  return JSON.stringify(body);
292
+ if (typeof body === "object" && (Object.getPrototypeOf(body) === Object.prototype || Object.getPrototypeOf(body) === null) && configError) {
293
+ throw configError("Request body is a plain object but Content-Type is not application/json");
294
+ }
48
295
  return body;
49
296
  }
50
297
  function getHeadersAsObject(headers) {
@@ -339,39 +586,61 @@ var isNetworkError = (e) => e instanceof NetworkError;
339
586
  var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
340
587
  var isConfigError = (e) => e instanceof ConfigError;
341
588
  var isActionError = (e) => e instanceof ActionError;
589
+ function isNonRetryableApiError(error) {
590
+ return error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError;
591
+ }
342
592
  // src/api-wrapper/builders.ts
343
593
  import { ZodError as ZodError2 } from "zod";
344
- function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url, configError) {
594
+ function formatQueryString(queryParams, doNotEncode) {
595
+ if (Object.keys(queryParams).length === 0)
596
+ return "";
597
+ return doNotEncode ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(queryParams).toString();
598
+ }
599
+ function buildUrl(options) {
600
+ const {
601
+ endpoint,
602
+ params,
603
+ queryParams,
604
+ baseApiUrls,
605
+ defaultBaseApiUrl,
606
+ url,
607
+ doNotEncodeQueryParams,
608
+ configError
609
+ } = options;
610
+ let finalUrl;
345
611
  if (url) {
346
- const finalUrl2 = replacePathParams(url, params, configError);
347
- const queryString2 = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
348
- return {
349
- finalUrl: finalUrl2,
350
- fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
351
- };
352
- }
353
- const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
354
- if (!defaultBaseUrl) {
355
- throw configError("At least one base API URL is required");
356
- }
357
- const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
358
- const configBaseUrl = baseApiUrls[baseUrlKey];
359
- if (!configBaseUrl) {
360
- throw configError(`Unknown base API URL: ${baseUrlKey}`);
612
+ finalUrl = replacePathParams(url, params, configError);
613
+ } else {
614
+ const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
615
+ if (!defaultBaseUrl) {
616
+ throw configError("At least one base API URL is required");
617
+ }
618
+ const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
619
+ const configBaseUrl = baseApiUrls[baseUrlKey];
620
+ if (!configBaseUrl) {
621
+ throw configError(`Unknown base API URL: ${baseUrlKey}`);
622
+ }
623
+ const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
624
+ const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
625
+ finalUrl = `${baseUrl}${replacePathParams(path, params, configError)}`;
361
626
  }
362
- const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
363
- const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
364
- const finalUrl = replacePathParams(`${baseUrl}${path}`, params, configError);
365
- const queryString = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
627
+ const queryString = formatQueryString(queryParams, doNotEncodeQueryParams);
366
628
  return {
367
629
  finalUrl,
368
630
  fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
369
631
  };
370
632
  }
371
- function buildQueryParamsRaw(endpoint, query) {
372
- const defaultParams = endpoint.reqDefaultQueryParams ?? {};
373
- const merged = { ...defaultParams, ...query ?? {} };
374
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
633
+ function buildPathParams(endpoint, params, ctx) {
634
+ if (!endpoint.reqParamsSchema)
635
+ return params;
636
+ try {
637
+ return endpoint.reqParamsSchema.parse(params);
638
+ } catch (error) {
639
+ if (error instanceof ZodError2) {
640
+ throw new RequestValidationError(error, "params", params, ctx());
641
+ }
642
+ throw error;
643
+ }
375
644
  }
376
645
  function buildQueryParams(endpoint, query, ctx) {
377
646
  const defaultParams = endpoint.reqDefaultQueryParams ?? {};
@@ -504,8 +773,8 @@ function createActionCache() {
504
773
  function makeDefineAction() {
505
774
  function defineAction(...args) {
506
775
  if (args.length === 0) {
507
- return (def2) => ({
508
- ...def2,
776
+ return (def) => ({
777
+ ...def,
509
778
  noRuntimeInput: false
510
779
  });
511
780
  }
@@ -536,6 +805,7 @@ var SENSITIVE_BODY_KEYS = new Set([
536
805
  "refresh_token",
537
806
  "secret",
538
807
  "client_secret",
808
+ "clientsecret",
539
809
  "apikey",
540
810
  "api_key"
541
811
  ]);
@@ -626,16 +896,14 @@ async function validateResponse(schema, data, ctx) {
626
896
  }
627
897
  }
628
898
  async function httpRequest(config, ctx) {
629
- const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
630
- const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
631
899
  const timeoutController = new AbortController;
632
900
  const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
633
901
  const signal = mergeSignals(timeoutController.signal, config.userSignal);
634
902
  try {
635
- return await config.fetchFn(fullUrl, {
903
+ return await config.fetchFn(config.fullUrl, {
636
904
  method: config.method,
637
905
  headers: config.headers,
638
- body: buildFetchBody(config.body, config.headers),
906
+ body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError(msg, ctx())),
639
907
  signal
640
908
  });
641
909
  } catch (error) {
@@ -648,6 +916,8 @@ async function httpRequest(config, ctx) {
648
916
  }
649
917
  throw new RequestAbortedError(ctx(), error);
650
918
  }
919
+ if (error instanceof ConfigError)
920
+ throw error;
651
921
  throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
652
922
  } finally {
653
923
  if (timeout)
@@ -684,35 +954,26 @@ async function readErrorResponse(response) {
684
954
  };
685
955
  }
686
956
  function getRetryDelay(params) {
957
+ const maxDelay = params.maxRetryDelayMs ?? 30000;
687
958
  const { retryDelay, attempt, response } = params;
688
959
  if (response && (response.status === 429 || response.status === 503)) {
689
960
  const retryAfter = response.headers.get("retry-after");
690
961
  if (retryAfter) {
691
962
  const seconds = Number(retryAfter);
692
- if (Number.isFinite(seconds))
693
- return seconds * 1000;
963
+ if (Number.isFinite(seconds)) {
964
+ return Math.min(Math.max(0, seconds * 1000), maxDelay);
965
+ }
694
966
  const dateMs = Date.parse(retryAfter);
695
967
  if (!Number.isNaN(dateMs)) {
696
- return Math.max(0, dateMs - Date.now());
968
+ return Math.min(Math.max(0, dateMs - Date.now()), maxDelay);
697
969
  }
698
970
  }
699
971
  }
700
- if (typeof retryDelay === "function")
701
- return retryDelay(attempt);
702
- return retryDelay;
972
+ const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
973
+ return Math.min(delayMs, maxDelay);
703
974
  }
704
975
  function shouldAttemptRetryOnError(error) {
705
- if (error instanceof HttpResponseError)
706
- return false;
707
- if (error instanceof ResponseValidationError)
708
- return false;
709
- if (error instanceof RequestValidationError)
710
- return false;
711
- if (error instanceof ConfigError)
712
- return false;
713
- if (error instanceof RequestAbortedError)
714
- return false;
715
- return true;
976
+ return !isNonRetryableApiError(error);
716
977
  }
717
978
  function shouldAttemptRetryOnHttp(retryCtx) {
718
979
  const { error, response } = retryCtx;
@@ -725,6 +986,7 @@ async function makeRequestWithRetry(args) {
725
986
  config,
726
987
  maxRetries,
727
988
  retryDelay,
989
+ maxRetryDelayMs,
728
990
  validateFn,
729
991
  shouldRetry,
730
992
  logger,
@@ -763,7 +1025,8 @@ async function makeRequestWithRetry(args) {
763
1025
  const delayMs = getRetryDelay({
764
1026
  retryDelay,
765
1027
  attempt: retryCount,
766
- response
1028
+ response,
1029
+ maxRetryDelayMs
767
1030
  });
768
1031
  await onRetry?.({
769
1032
  ...retryCtx,
@@ -778,12 +1041,12 @@ async function makeRequestWithRetry(args) {
778
1041
  throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
779
1042
  }
780
1043
  return {
781
- validatedData: await validateFn(response),
1044
+ validatedData: await validateFn(response, retryCount),
782
1045
  retryCount,
783
1046
  httpStatus: response.status
784
1047
  };
785
1048
  } catch (error) {
786
- if (error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError) {
1049
+ if (isNonRetryableApiError(error)) {
787
1050
  throw error;
788
1051
  }
789
1052
  const retryCtx = {
@@ -800,7 +1063,8 @@ async function makeRequestWithRetry(args) {
800
1063
  const delayMs = getRetryDelay({
801
1064
  retryDelay,
802
1065
  attempt: retryCount,
803
- response: undefined
1066
+ response: undefined,
1067
+ maxRetryDelayMs
804
1068
  });
805
1069
  await onRetry?.({
806
1070
  ...retryCtx,
@@ -814,19 +1078,21 @@ async function makeRequestWithRetry(args) {
814
1078
  }
815
1079
  async function validateAndTransformResponse(endpoint, response, ctx) {
816
1080
  const responseHeaders = getHeadersAsObject(response.headers);
1081
+ const text = await response.text();
1082
+ const responseSizeBytes = new TextEncoder().encode(text).length;
817
1083
  let responseData;
818
1084
  if (hasJsonContentType(responseHeaders)) {
819
- const text = await response.text();
820
1085
  try {
821
1086
  responseData = text === "" ? undefined : JSON.parse(text);
822
1087
  } catch (error) {
823
1088
  throw new ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
824
1089
  }
825
1090
  } else {
826
- responseData = await response.text();
1091
+ responseData = text;
827
1092
  }
828
1093
  const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
829
- return validateResponse(endpoint.resSchema, transformedData, ctx);
1094
+ const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
1095
+ return { data, responseSizeBytes };
830
1096
  }
831
1097
  function defaultShouldRetry(ctx) {
832
1098
  if (ctx.error)
@@ -845,6 +1111,28 @@ function isActionInvokeOptionsOnly(value) {
845
1111
  return false;
846
1112
  return Object.keys(value).every((k) => k === "signal" || k === "meta");
847
1113
  }
1114
+ function resolveActionInvocation(action, actionName, first, second) {
1115
+ if (!action.noRuntimeInput) {
1116
+ return { input: first, options: second ?? {} };
1117
+ }
1118
+ if (first === undefined || first === null) {
1119
+ return { input: undefined, options: second ?? {} };
1120
+ }
1121
+ if (second !== undefined) {
1122
+ return { input: undefined, options: second };
1123
+ }
1124
+ if (isActionInvokeOptionsOnly(first)) {
1125
+ return { input: undefined, options: first };
1126
+ }
1127
+ throw new ConfigError(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
1128
+ endpoint: actionName,
1129
+ method: "GET",
1130
+ url: "",
1131
+ attempt: 0,
1132
+ maxRetries: 0,
1133
+ elapsedMs: 0
1134
+ });
1135
+ }
848
1136
  function createApiClientImpl(apiConfig) {
849
1137
  let actions = {};
850
1138
  const cache = createActionCache();
@@ -873,22 +1161,12 @@ function createApiClientImpl(apiConfig) {
873
1161
  throw await reportApiError(new ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
874
1162
  }
875
1163
  baseContext.method = endpoint.method;
876
- let finalUrl;
877
- let fullUrl;
878
- try {
879
- const built = buildUrl(endpoint, rawInput.reqParams, rawInput.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url, (msg) => new ConfigError(msg, ctx()));
880
- finalUrl = built.finalUrl;
881
- fullUrl = built.fullUrl;
882
- } catch (e) {
883
- if (e instanceof ApiError)
884
- throw await reportApiError(e, apiConfig);
885
- throw e;
886
- }
887
- baseContext.url = fullUrl;
1164
+ let pathParams;
888
1165
  let queryParams;
889
1166
  let body;
890
1167
  let headers;
891
1168
  try {
1169
+ pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
892
1170
  queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
893
1171
  body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
894
1172
  headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
@@ -897,6 +1175,25 @@ function createApiClientImpl(apiConfig) {
897
1175
  throw await reportApiError(e, apiConfig);
898
1176
  throw e;
899
1177
  }
1178
+ let fullUrl;
1179
+ try {
1180
+ const built = buildUrl({
1181
+ endpoint,
1182
+ params: pathParams,
1183
+ queryParams,
1184
+ baseApiUrls: apiConfig.baseApiUrls,
1185
+ defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
1186
+ url: input.url,
1187
+ doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
1188
+ configError: (msg) => new ConfigError(msg, ctx())
1189
+ });
1190
+ fullUrl = built.fullUrl;
1191
+ } catch (e) {
1192
+ if (e instanceof ApiError)
1193
+ throw await reportApiError(e, apiConfig);
1194
+ throw e;
1195
+ }
1196
+ baseContext.url = fullUrl;
900
1197
  baseContext.requestHeaders = headers;
901
1198
  baseContext.requestBody = body;
902
1199
  if (apiConfig.onRequest) {
@@ -921,11 +1218,9 @@ function createApiClientImpl(apiConfig) {
921
1218
  }
922
1219
  const config = {
923
1220
  method: endpoint.method,
924
- url: finalUrl,
925
- queryParams,
1221
+ fullUrl,
926
1222
  body,
927
1223
  headers,
928
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
929
1224
  timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
930
1225
  fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
931
1226
  userSignal: input.signal
@@ -935,7 +1230,8 @@ function createApiClientImpl(apiConfig) {
935
1230
  config,
936
1231
  maxRetries,
937
1232
  retryDelay: apiConfig.retryDelayMs ?? 300,
938
- validateFn: (response) => validateAndTransformResponse(endpoint, response, ctx),
1233
+ maxRetryDelayMs: apiConfig.maxRetryDelayMs,
1234
+ validateFn: (response, attempt) => validateAndTransformResponse(endpoint, response, (over) => ctx({ attempt, ...over })),
939
1235
  shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
940
1236
  logger: apiConfig.logger,
941
1237
  ctx,
@@ -947,10 +1243,10 @@ function createApiClientImpl(apiConfig) {
947
1243
  onRetry: apiConfig.onRetry
948
1244
  });
949
1245
  const responseTimeMs = elapsed();
950
- const responseSizeMb = calculateSizeInMb(result.validatedData);
1246
+ const responseSizeMb = result.validatedData.responseSizeBytes / (1024 * 1024);
951
1247
  const requestBodySizeMb = calculateSizeInMb(config.body);
952
1248
  return {
953
- data: result.validatedData,
1249
+ data: result.validatedData.data,
954
1250
  requestBodySizeMb,
955
1251
  responseSizeMb,
956
1252
  responseTimeMs,
@@ -1006,22 +1302,14 @@ function createApiClientImpl(apiConfig) {
1006
1302
  throw await reportApiError(wrapped, apiConfig);
1007
1303
  }
1008
1304
  }
1305
+ async function dispatchAction(name, first, second) {
1306
+ const action = actions[name];
1307
+ const { input, options } = resolveActionInvocation(action, name, first, second);
1308
+ return invokeAction(name, input, options);
1309
+ }
1009
1310
  const request = async (key, input, options) => {
1010
1311
  if (key in actions) {
1011
- const action = actions[key];
1012
- if (action.noRuntimeInput) {
1013
- if (input === undefined || input === null) {
1014
- return invokeAction(key, undefined, options ?? {});
1015
- }
1016
- if (options !== undefined) {
1017
- return invokeAction(key, undefined, options);
1018
- }
1019
- if (isActionInvokeOptionsOnly(input)) {
1020
- return invokeAction(key, undefined, input);
1021
- }
1022
- return invokeAction(key, undefined, {});
1023
- }
1024
- return invokeAction(key, input, options);
1312
+ return dispatchAction(key, input, options);
1025
1313
  }
1026
1314
  return invokeEndpoint(key, input ?? {});
1027
1315
  };
@@ -1035,22 +1323,7 @@ function createApiClientImpl(apiConfig) {
1035
1323
  return (input) => invokeEndpoint(prop, input ?? {});
1036
1324
  }
1037
1325
  if (prop in actions) {
1038
- const action = actions[prop];
1039
- if (action.noRuntimeInput) {
1040
- return (first, second) => {
1041
- if (first === undefined || first === null) {
1042
- return invokeAction(prop, undefined, second ?? {});
1043
- }
1044
- if (second !== undefined) {
1045
- return invokeAction(prop, undefined, second);
1046
- }
1047
- if (isActionInvokeOptionsOnly(first)) {
1048
- return invokeAction(prop, undefined, first);
1049
- }
1050
- return invokeAction(prop, undefined, {});
1051
- };
1052
- }
1053
- return (input, opt) => invokeAction(prop, input, opt);
1326
+ return (first, second) => dispatchAction(prop, first, second);
1054
1327
  }
1055
1328
  return;
1056
1329
  }
@@ -1078,72 +1351,227 @@ function createApiClientImpl(apiConfig) {
1078
1351
  return proxy;
1079
1352
  }
1080
1353
  var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
1081
- // src/secrets/infisical.ts
1354
+ // src/secrets/infisical-cli.ts
1355
+ async function readInfisicalCliToken(options) {
1356
+ const { execFile } = await import("node:child_process").catch(() => {
1357
+ throw new SecretLoadError("CLI", "Infisical CLI fallback requires a runtime with subprocess support. Provide Infisical client credentials or a custom secret loader.");
1358
+ });
1359
+ options.signal.throwIfAborted();
1360
+ const env = {
1361
+ ...getProcessEnvironment(),
1362
+ INFISICAL_DISABLE_UPDATE_CHECK: "true"
1363
+ };
1364
+ delete env.INFISICAL_TOKEN;
1365
+ delete env.INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN;
1366
+ delete env.TOKEN;
1367
+ try {
1368
+ const stdout = await new Promise((resolve, reject) => {
1369
+ const child = execFile("infisical", [
1370
+ "user",
1371
+ "get",
1372
+ "token",
1373
+ "--plain",
1374
+ `--domain=${options.siteUrl}`,
1375
+ "--silent"
1376
+ ], {
1377
+ env,
1378
+ encoding: "utf8",
1379
+ shell: false,
1380
+ timeout: options.timeoutMs ?? 30000,
1381
+ signal: options.signal,
1382
+ killSignal: "SIGKILL",
1383
+ maxBuffer: 10485760,
1384
+ windowsHide: true
1385
+ }, (error, output) => {
1386
+ if (error) {
1387
+ reject(error);
1388
+ } else {
1389
+ resolve(output);
1390
+ }
1391
+ });
1392
+ child.stdin?.end();
1393
+ });
1394
+ return stdout.trim();
1395
+ } catch (error) {
1396
+ if (options.signal.aborted) {
1397
+ throw options.signal.reason;
1398
+ }
1399
+ const failure = error;
1400
+ if (failure?.code === "ENOENT") {
1401
+ throw new SecretLoadError("CLI", "Infisical CLI not found. Install infisical and ensure it is on PATH, or provide Infisical client credentials.");
1402
+ }
1403
+ if (failure?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
1404
+ throw new SecretLoadError("CLI", "Infisical CLI output exceeded the 10 MiB limit.");
1405
+ }
1406
+ if (failure?.killed) {
1407
+ throw new SecretLoadError("TIMEOUT", `Infisical CLI timed out after ${(options.timeoutMs ?? 30000) / 1000} seconds or was terminated.`);
1408
+ }
1409
+ throw new SecretLoadError("CLI", `Infisical CLI session token failed. Run infisical login --domain=${options.siteUrl} and verify access to the configured organization, project, and environment.`);
1410
+ }
1411
+ }
1412
+
1413
+ // src/secrets/infisical-config.ts
1414
+ function validateInfisicalScope(options) {
1415
+ validateInfisicalOrganizationId(options.organizationId);
1416
+ let url;
1417
+ try {
1418
+ url = new URL(options.siteUrl);
1419
+ } catch {
1420
+ throw new SecretLoadError("CONFIGURATION", "Invalid Infisical siteUrl.");
1421
+ }
1422
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
1423
+ throw new SecretLoadError("CONFIGURATION", "Infisical siteUrl must be an HTTP(S) URL without credentials, query, or fragment.");
1424
+ }
1425
+ if (!options.projectId.trim() || !options.environment.trim() || !(options.secretPath ?? "/").startsWith("/")) {
1426
+ throw new SecretLoadError("CONFIGURATION", "Infisical requires a projectId, environment, and absolute secretPath.");
1427
+ }
1428
+ }
1429
+ function validateInfisicalOrganizationId(organizationId) {
1430
+ if (typeof organizationId !== "string" || !organizationId.trim() || /\s/.test(organizationId)) {
1431
+ throw new SecretLoadError("CONFIGURATION", "Infisical organizationId is required and must be a nonempty ID without whitespace.");
1432
+ }
1433
+ }
1434
+
1435
+ // src/secrets/infisical-session.ts
1082
1436
  import { z } from "zod";
1437
+ var sessionClaimsSchema = z.object({
1438
+ organizationId: z.string().optional(),
1439
+ subOrganizationId: z.string().optional(),
1440
+ exp: z.number().finite().optional()
1441
+ });
1442
+ function parseInfisicalSessionToken(token) {
1443
+ let claims;
1444
+ try {
1445
+ if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
1446
+ throw new Error("Invalid token format");
1447
+ }
1448
+ const payload = token.split(".")[1];
1449
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
1450
+ claims = sessionClaimsSchema.parse(JSON.parse(json));
1451
+ } catch {
1452
+ throw new SecretLoadError("AUTHENTICATION", "Infisical returned an invalid user session token. Run infisical login for the configured domain.");
1453
+ }
1454
+ if (claims.exp !== undefined && claims.exp <= Date.now() / 1000) {
1455
+ throw new SecretLoadError("AUTHENTICATION", "Infisical user session has expired. Run infisical login for the configured domain.");
1456
+ }
1457
+ return { organizationId: claims.subOrganizationId ?? claims.organizationId };
1458
+ }
1459
+
1460
+ // src/secrets/infisical.ts
1083
1461
  var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
1084
1462
  var defaultFetch = globalThis.fetch;
1085
- var infisicalAuthResponseSchema = z.object({
1086
- accessToken: z.string()
1463
+ var infisicalAuthResponseSchema = z2.object({ accessToken: z2.string() });
1464
+ var infisicalSecretSchema = z2.object({
1465
+ secretKey: z2.string(),
1466
+ secretValue: z2.string()
1087
1467
  });
1088
- var infisicalSecretSchema = z.object({
1089
- secretKey: z.string(),
1090
- secretValue: z.string()
1468
+ var infisicalSecretsResponseSchema = z2.object({
1469
+ secrets: z2.array(infisicalSecretSchema)
1091
1470
  });
1092
- var infisicalSecretsResponseSchema = z.object({
1093
- secrets: z.array(infisicalSecretSchema)
1094
- });
1095
- async function fetchInfisicalSecrets({
1096
- keys,
1097
- projectId,
1098
- environment,
1099
- siteUrl,
1100
- clientId,
1101
- clientSecret
1102
- }) {
1103
- const infisical = createInfisicalApiClient(siteUrl);
1104
- const { data: auth } = await infisical.request("login", {
1105
- reqBody: {
1106
- clientId,
1107
- clientSecret
1471
+ async function fetchInfisicalSecrets(options) {
1472
+ validateInfisicalScope(options);
1473
+ return withSecretDeadline(options, async (signal) => {
1474
+ let operation = "login";
1475
+ try {
1476
+ const infisical = createInfisicalApiClient(options.siteUrl);
1477
+ const { data: auth } = await infisical.request("login", {
1478
+ signal,
1479
+ reqBody: {
1480
+ clientId: options.clientId,
1481
+ clientSecret: options.clientSecret
1482
+ }
1483
+ });
1484
+ signal.throwIfAborted();
1485
+ operation = "listSecrets";
1486
+ return await listInfisicalSecrets(infisical, auth.accessToken, {
1487
+ ...options,
1488
+ signal
1489
+ });
1490
+ } catch (error) {
1491
+ throwInfisicalHttpError(error, operation, signal);
1108
1492
  }
1109
1493
  });
1494
+ }
1495
+ async function loadInfisicalSecrets(options) {
1496
+ validateInfisicalScope(options);
1497
+ const env = getProcessEnvironment();
1498
+ const clientId = env?.[options.clientIdEnvKey];
1499
+ const clientSecret = env?.[options.clientSecretEnvKey];
1500
+ const mode = options.auth ?? "auto";
1501
+ if (!["auto", "http", "cli"].includes(mode)) {
1502
+ throw new SecretLoadError("CONFIGURATION", "Invalid Infisical auth mode.");
1503
+ }
1504
+ const useHttp = mode === "http" || mode === "auto" && Boolean(clientId && clientSecret);
1505
+ if (useHttp) {
1506
+ if (!clientId || !clientSecret) {
1507
+ throw new SecretLoadError("AUTHENTICATION", "Infisical HTTP authentication requires both client credentials.");
1508
+ }
1509
+ options.reportSource?.("Infisical HTTP");
1510
+ return fetchInfisicalSecrets({ ...options, clientId, clientSecret });
1511
+ }
1512
+ options.reportSource?.(`Infisical CLI session (organization=${options.organizationId} project=${options.projectId} environment=${options.environment})`);
1513
+ return withSecretDeadline(options, (signal) => fetchInfisicalSessionSecrets({ ...options, signal }));
1514
+ }
1515
+ async function fetchInfisicalSessionSecrets(options) {
1516
+ const { signal } = options;
1517
+ let token = await readInfisicalCliToken(options);
1518
+ signal.throwIfAborted();
1519
+ const session = parseInfisicalSessionToken(token);
1520
+ const infisical = createInfisicalApiClient(options.siteUrl);
1521
+ let operation = "selectOrganization";
1522
+ try {
1523
+ if (session.organizationId !== options.organizationId) {
1524
+ const { data } = await infisical.request("selectOrganization", {
1525
+ signal,
1526
+ reqHeaders: {
1527
+ Authorization: `Bearer ${token}`,
1528
+ "User-Agent": "hanzio/secrets"
1529
+ },
1530
+ reqBody: { organizationId: options.organizationId }
1531
+ });
1532
+ signal.throwIfAborted();
1533
+ if (data.isMfaEnabled) {
1534
+ throw new SecretLoadError("AUTHENTICATION", "Infisical requires MFA for this organization. Complete infisical login for the target organization and configured domain before loading secrets.", { operation });
1535
+ }
1536
+ const scopedSession = parseInfisicalSessionToken(data.token);
1537
+ if (scopedSession.organizationId !== options.organizationId) {
1538
+ throw new SecretLoadError("AUTHENTICATION", "Infisical returned a session for a different organization.", { operation });
1539
+ }
1540
+ token = data.token;
1541
+ }
1542
+ signal.throwIfAborted();
1543
+ operation = "listSecrets";
1544
+ return await listInfisicalSecrets(infisical, token, options);
1545
+ } catch (error) {
1546
+ throwInfisicalHttpError(error, operation, signal);
1547
+ }
1548
+ }
1549
+ async function listInfisicalSecrets(infisical, token, options) {
1110
1550
  const { data } = await infisical.request("listSecrets", {
1111
- reqHeaders: {
1112
- Authorization: `Bearer ${auth.accessToken}`
1113
- },
1551
+ signal: options.signal,
1552
+ reqHeaders: { Authorization: `Bearer ${token}` },
1114
1553
  reqQuery: {
1115
- environment,
1116
- projectId,
1117
- secretPath: "/",
1554
+ environment: options.environment,
1555
+ projectId: options.projectId,
1556
+ secretPath: options.secretPath ?? "/",
1118
1557
  viewSecretValue: true,
1119
1558
  expandSecretReferences: true,
1120
1559
  includeImports: true
1121
1560
  }
1122
1561
  });
1123
- const wantedKeys = new Set(keys);
1562
+ const wantedKeys = new Set(options.keys);
1124
1563
  return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
1125
1564
  }
1126
- async function loadInfisicalSecrets({
1127
- keys,
1128
- projectId,
1129
- environment,
1130
- siteUrl,
1131
- clientIdEnvKey,
1132
- clientSecretEnvKey
1133
- }) {
1134
- const clientId = process.env[clientIdEnvKey];
1135
- const clientSecret = process.env[clientSecretEnvKey];
1136
- if (!clientId || !clientSecret) {
1137
- throw new Error("Missing Infisical credentials");
1138
- }
1139
- return fetchInfisicalSecrets({
1140
- keys,
1141
- projectId,
1142
- environment,
1143
- siteUrl,
1144
- clientId,
1145
- clientSecret
1146
- });
1565
+ function throwInfisicalHttpError(error, operation, signal) {
1566
+ if (signal.aborted) {
1567
+ throw signal.reason;
1568
+ }
1569
+ if (error instanceof SecretLoadError) {
1570
+ throw error;
1571
+ }
1572
+ const status = error instanceof HttpResponseError ? error.status : undefined;
1573
+ const guidance = operation === "selectOrganization" && (status === 401 || status === 403) ? " Run infisical login for the target organization and configured domain, then verify your access." : "";
1574
+ throw new SecretLoadError("HTTP", `Infisical HTTP ${operation} failed${status ? ` (status ${status})` : ""}.${guidance}`, { operation, status });
1147
1575
  }
1148
1576
  function createInfisicalApiClient(siteUrl) {
1149
1577
  return createApiClient({
@@ -1155,12 +1583,26 @@ function createInfisicalApiClient(siteUrl) {
1155
1583
  fetch: getFetch(),
1156
1584
  retries: 1,
1157
1585
  endpoints: {
1586
+ selectOrganization: {
1587
+ method: "POST",
1588
+ path: "/api/v3/auth/select-organization",
1589
+ reqHeadersSchema: z2.object({
1590
+ Authorization: z2.string(),
1591
+ "User-Agent": z2.string()
1592
+ }),
1593
+ reqBodySchema: z2.object({ organizationId: z2.string() }),
1594
+ resSchema: z2.object({
1595
+ token: z2.string().min(1),
1596
+ isMfaEnabled: z2.boolean().optional()
1597
+ }),
1598
+ reqBodyFormat: "json"
1599
+ },
1158
1600
  login: {
1159
1601
  method: "POST",
1160
1602
  path: "/api/v1/auth/universal-auth/login",
1161
- reqBodySchema: z.object({
1162
- clientId: z.string(),
1163
- clientSecret: z.string()
1603
+ reqBodySchema: z2.object({
1604
+ clientId: z2.string(),
1605
+ clientSecret: z2.string()
1164
1606
  }),
1165
1607
  resSchema: infisicalAuthResponseSchema,
1166
1608
  reqBodyFormat: "json"
@@ -1168,16 +1610,16 @@ function createInfisicalApiClient(siteUrl) {
1168
1610
  listSecrets: {
1169
1611
  method: "GET",
1170
1612
  path: "/api/v4/secrets",
1171
- reqHeadersSchema: z.object({
1172
- Authorization: z.string()
1613
+ reqHeadersSchema: z2.object({
1614
+ Authorization: z2.string()
1173
1615
  }),
1174
- reqQuerySchema: z.object({
1175
- projectId: z.string(),
1176
- environment: z.string(),
1177
- secretPath: z.string(),
1178
- viewSecretValue: z.boolean(),
1179
- expandSecretReferences: z.boolean(),
1180
- includeImports: z.boolean()
1616
+ reqQuerySchema: z2.object({
1617
+ projectId: z2.string(),
1618
+ environment: z2.string(),
1619
+ secretPath: z2.string(),
1620
+ viewSecretValue: z2.boolean(),
1621
+ expandSecretReferences: z2.boolean(),
1622
+ includeImports: z2.boolean()
1181
1623
  }),
1182
1624
  resSchema: infisicalSecretsResponseSchema
1183
1625
  }
@@ -1185,21 +1627,25 @@ function createInfisicalApiClient(siteUrl) {
1185
1627
  });
1186
1628
  }
1187
1629
  function getFetch() {
1188
- return globalThis.fetch ?? defaultFetch;
1630
+ const fetchFn = globalThis.fetch ?? defaultFetch;
1631
+ return (input, init) => {
1632
+ init?.signal?.throwIfAborted();
1633
+ return fetchFn(input, init);
1634
+ };
1189
1635
  }
1190
-
1191
- // src/secrets/loaders.ts
1636
+ // src/secrets/worker-loader.ts
1192
1637
  function cloudflareWorkerEnvLoader(workerEnv) {
1193
- return async (ctx) => {
1194
- const out = {};
1638
+ const loader = async (ctx) => {
1639
+ const out = Object.create(null);
1195
1640
  for (const key of ctx.keys) {
1196
- const str = workerBindingToString(workerEnv[key]);
1641
+ const str = workerBindingToString(Object.hasOwn(workerEnv, key) ? workerEnv[key] : undefined);
1197
1642
  if (str !== undefined) {
1198
1643
  out[key] = str;
1199
1644
  }
1200
1645
  }
1201
1646
  return out;
1202
1647
  };
1648
+ return Object.assign(loader, { source: "Cloudflare Worker bindings" });
1203
1649
  }
1204
1650
  function workerBindingToString(value) {
1205
1651
  if (value === undefined || value === null) {
@@ -1216,266 +1662,88 @@ function workerBindingToString(value) {
1216
1662
  }
1217
1663
  return String(value);
1218
1664
  }
1665
+
1666
+ // src/secrets/loaders.ts
1219
1667
  function infisicalLoader(config) {
1220
- return async (ctx) => fetchInfisicalSecrets({
1668
+ validateInfisicalOrganizationId(config.organizationId);
1669
+ const loader = async (ctx) => fetchInfisicalSecrets({
1221
1670
  keys: ctx.keys,
1222
1671
  projectId: config.projectId,
1672
+ organizationId: config.organizationId,
1223
1673
  environment: ctx.environment,
1224
1674
  siteUrl: config.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
1225
1675
  clientId: config.clientId,
1226
- clientSecret: config.clientSecret
1676
+ clientSecret: config.clientSecret,
1677
+ secretPath: config.secretPath,
1678
+ timeoutMs: config.timeoutMs ?? ctx.timeoutMs,
1679
+ signal: ctx.signal
1227
1680
  });
1681
+ return Object.assign(loader, { source: "Infisical HTTP" });
1228
1682
  }
1229
1683
  async function processEnvLoader(_ctx) {
1230
1684
  return {};
1231
1685
  }
1232
-
1686
+ Object.assign(processEnvLoader, { source: "process.env" });
1233
1687
  // src/secrets/vite.ts
1234
- function getViteDefine(secretSet) {
1235
- return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
1236
- `import.meta.env.${key}`,
1237
- JSON.stringify(value)
1238
- ]));
1688
+ function getViteDefine(secretSet, options = {}) {
1689
+ const values = secretSet.secrets();
1690
+ const keys = options.publicKeys ?? Object.keys(values);
1691
+ if (!options.publicKeys && keys.some((key) => !key.startsWith("VITE_"))) {
1692
+ throw new SecretLoadError("CONFIGURATION", "Vite secret sets must contain only VITE_ keys, or provide an explicit publicKeys list.");
1693
+ }
1694
+ for (const key of keys) {
1695
+ if (!Object.hasOwn(values, key) || !/^[A-Za-z_$][\w$]*$/.test(key)) {
1696
+ throw new SecretLoadError("CONFIGURATION", "Vite publicKeys must reference configured keys with valid JavaScript identifiers.");
1697
+ }
1698
+ if (["MODE", "BASE_URL", "PROD", "DEV", "SSR"].includes(key)) {
1699
+ throw new SecretLoadError("CONFIGURATION", "Vite built-in environment constants cannot be overridden by secrets.");
1700
+ }
1701
+ }
1702
+ return Object.fromEntries(keys.map((key) => [`import.meta.env.${key}`, JSON.stringify(values[key])]));
1239
1703
  }
1240
- function viteSecretSetPlugin(secretSet) {
1704
+ function viteSecretSetPlugin(secretSet, options = {}) {
1241
1705
  return {
1242
1706
  name: "hanzio-secret-set",
1243
- config: () => ({
1244
- define: getViteDefine(secretSet)
1245
- })
1246
- };
1247
- }
1248
-
1249
- // src/cool-console-log/index.ts
1250
- var terminalColors = {
1251
- reset: "\x1B[0m",
1252
- bold: "\x1B[1m",
1253
- dim: "\x1B[2m",
1254
- italic: "\x1B[3m",
1255
- underline: "\x1B[4m",
1256
- black: "\x1B[30m",
1257
- red: "\x1B[31m",
1258
- green: "\x1B[32m",
1259
- yellow: "\x1B[33m",
1260
- blue: "\x1B[34m",
1261
- magenta: "\x1B[35m",
1262
- cyan: "\x1B[36m",
1263
- white: "\x1B[37m",
1264
- gray: "\x1B[90m",
1265
- brightRed: "\x1B[91m",
1266
- brightGreen: "\x1B[92m",
1267
- brightYellow: "\x1B[93m",
1268
- brightBlue: "\x1B[94m",
1269
- brightMagenta: "\x1B[95m",
1270
- brightCyan: "\x1B[96m",
1271
- brightWhite: "\x1B[97m",
1272
- bgBlack: "\x1B[40m",
1273
- bgRed: "\x1B[41m",
1274
- bgGreen: "\x1B[42m",
1275
- bgYellow: "\x1B[43m",
1276
- bgBlue: "\x1B[44m",
1277
- bgMagenta: "\x1B[45m",
1278
- bgCyan: "\x1B[46m",
1279
- bgWhite: "\x1B[47m"
1280
- };
1281
- var levelColors = {
1282
- debug: { badge: terminalColors.gray, text: terminalColors.dim },
1283
- info: { badge: terminalColors.cyan, text: terminalColors.reset },
1284
- warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
1285
- error: { badge: terminalColors.red, text: terminalColors.brightRed }
1286
- };
1287
- function formatAttributes(attrs) {
1288
- if (!attrs || Object.keys(attrs).length === 0)
1289
- return "";
1290
- const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
1291
- return ` ${formatted}`;
1292
- }
1293
- function createCoolLogger(options = {}) {
1294
- const { colorsInDevOnly = true, onLog } = options;
1295
- const isDev = true;
1296
- const useColors = colorsInDevOnly ? isDev : true;
1297
- const createLogMethod = (level) => (message, attributes) => {
1298
- onLog?.(level, message, attributes);
1299
- if (level === "debug" && !isDev)
1300
- return;
1301
- if (useColors) {
1302
- const { badge, text } = levelColors[level];
1303
- const label = level.toUpperCase().padEnd(5);
1304
- const attrStr = formatAttributes(attributes);
1305
- console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
1306
- } else {
1307
- console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
1308
- }
1309
- };
1310
- return {
1311
- debug: createLogMethod("debug"),
1312
- info: createLogMethod("info"),
1313
- warn: createLogMethod("warn"),
1314
- error: createLogMethod("error")
1707
+ config: () => ({ define: getViteDefine(secretSet, options) })
1315
1708
  };
1316
1709
  }
1317
- function logOperationSummary(operationName, durationMs, success, options = {}) {
1318
- const { devOnly = true, error } = options;
1319
- const isDev = true;
1320
- if (devOnly && !isDev)
1321
- return;
1322
- const c = terminalColors;
1323
- const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
1324
- const statusColor = success ? c.green : c.red;
1325
- const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
1326
- const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
1327
- console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
1328
- }
1329
- function colorize(text, color, ...modifiers) {
1330
- const isDev = true;
1331
- if (!isDev)
1332
- return text;
1333
- const colorCodes = [
1334
- terminalColors[color],
1335
- ...modifiers.map((m) => terminalColors[m])
1336
- ].join("");
1337
- return `${colorCodes}${text}${terminalColors.reset}`;
1338
- }
1339
- function logBanner(text, color = "cyan", options = {}) {
1340
- const { devOnly = true } = options;
1341
- const isDev = true;
1342
- if (devOnly && !isDev) {
1343
- console.log(`=== ${text} ===`);
1344
- return;
1345
- }
1346
- const c = terminalColors;
1347
- const padding = 2;
1348
- const line = "═".repeat(text.length + padding * 2);
1349
- console.log(`${c[color]}╔${line}╗${c.reset}`);
1350
- console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
1351
- console.log(`${c[color]}╚${line}╝${c.reset}`);
1352
- }
1353
1710
 
1354
1711
  // src/secrets/index.ts
1355
- var logger = createCoolLogger();
1356
1712
  function getSecretEnvironment() {
1357
- const env = process.env.SECRETS_ENV || "development";
1358
- if (env === "production" || env === "prod")
1359
- return "prod";
1360
- if (env === "staging")
1361
- return "staging";
1362
- return "dev";
1713
+ return resolveSecretEnvironment(undefined, getProcessEnvironment());
1363
1714
  }
1364
- async function defineSecretSet(keys, options) {
1365
- const keySet = [...new Set(keys)];
1366
- const resolvedOptions = resolveSecretSetOptions(options);
1367
- let cachedSecrets = null;
1368
- const loadSecrets = async (force = false) => {
1369
- const nextSecrets = await loadAndMergeSecrets({
1370
- keySet,
1371
- cachedSecrets,
1372
- force,
1373
- ...resolvedOptions
1374
- });
1375
- cachedSecrets = nextSecrets;
1376
- return nextSecrets;
1377
- };
1378
- const secretSet = createSecretSet({
1379
- getCachedSecrets: () => cachedSecrets,
1380
- reload: loadSecrets
1381
- });
1382
- await loadSecrets();
1383
- return secretSet;
1715
+ async function defineSecretSet2(keys, options) {
1716
+ let loader;
1717
+ if (!options.loader) {
1718
+ validateInfisicalOrganizationId(options.organizationId);
1719
+ loader = createDefaultInfisicalLoader(options);
1720
+ } else {
1721
+ loader = options.loader;
1722
+ }
1723
+ return defineSecretSet(keys, { ...options, loader }, getProcessEnvironment());
1384
1724
  }
1385
- function resolveSecretSetOptions(options) {
1386
- const environment = resolveEnvironment(options.environment);
1387
- const loader = options.loader ? options.loader : (context) => loadInfisicalSecrets({
1725
+ function createDefaultInfisicalLoader(options) {
1726
+ return (context) => loadInfisicalSecrets({
1388
1727
  ...context,
1389
1728
  projectId: options.projectId,
1729
+ organizationId: options.organizationId,
1390
1730
  siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
1731
+ secretPath: options.secretPath,
1732
+ auth: options.auth,
1391
1733
  clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
1392
1734
  clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
1393
1735
  });
1394
- return {
1395
- loader,
1396
- environment
1397
- };
1398
- }
1399
- async function loadAndMergeSecrets({
1400
- keySet,
1401
- cachedSecrets,
1402
- loader,
1403
- environment,
1404
- force
1405
- }) {
1406
- const processEnvSecrets = readProcessEnvSecrets(keySet, force ? cachedSecrets : null);
1407
- const keysMissingFromEnv = keySet.filter((key) => !processEnvSecrets[key]);
1408
- logSecretEnvironment(environment, keySet.length);
1409
- const loaderKeys = force ? keySet : keysMissingFromEnv;
1410
- const loadedSecrets = loaderKeys.length === 0 ? {} : await loader({
1411
- keys: loaderKeys,
1412
- environment
1413
- });
1414
- const missingKeys = keySet.filter((key) => !(processEnvSecrets[key] ?? loadedSecrets[key]));
1415
- if (missingKeys.length > 0) {
1416
- throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
1417
- }
1418
- const nextSecrets = Object.fromEntries(keySet.map((key) => [
1419
- key,
1420
- processEnvSecrets[key] ?? loadedSecrets[key]
1421
- ]));
1422
- for (const [key, value] of Object.entries(nextSecrets)) {
1423
- process.env[key] = value;
1424
- }
1425
- return nextSecrets;
1426
- }
1427
- function createSecretSet({
1428
- getCachedSecrets,
1429
- reload
1430
- }) {
1431
- const secretSet = {
1432
- secret(key) {
1433
- const value = getCachedSecrets()?.[key] ?? process.env[key];
1434
- if (!value) {
1435
- throw new Error(`Secret ${key} not found`);
1436
- }
1437
- return value;
1438
- },
1439
- secrets() {
1440
- const cachedSecrets = getCachedSecrets();
1441
- if (!cachedSecrets) {
1442
- throw new Error("Secrets not initialized.");
1443
- }
1444
- return { ...cachedSecrets };
1445
- },
1446
- async reload() {
1447
- await reload(true);
1448
- return secretSet;
1449
- }
1450
- };
1451
- return secretSet;
1452
- }
1453
- function resolveEnvironment(environment) {
1454
- if (typeof environment === "function")
1455
- return environment();
1456
- return environment ?? getSecretEnvironment();
1457
- }
1458
- function logSecretEnvironment(environment, secretCount) {
1459
- logger.info(colorize("Loading secrets", "cyan", "bold"), {
1460
- environment: colorize(environment, "brightCyan", "bold"),
1461
- count: secretCount
1462
- });
1463
- }
1464
- function readProcessEnvSecrets(keys, previousSecrets = null) {
1465
- return Object.fromEntries(keys.filter((key) => {
1466
- const value = process.env[key];
1467
- return value && value !== previousSecrets?.[key];
1468
- }).map((key) => [key, process.env[key]]));
1469
1736
  }
1470
1737
  export {
1471
- viteSecretSetPlugin,
1472
- processEnvLoader,
1473
- loadInfisicalSecrets,
1474
- infisicalLoader,
1475
- getViteDefine,
1476
- getSecretEnvironment,
1477
- fetchInfisicalSecrets,
1478
- defineSecretSet,
1738
+ DEFAULT_INFISICAL_SITE_URL,
1739
+ SecretLoadError,
1479
1740
  cloudflareWorkerEnvLoader,
1480
- DEFAULT_INFISICAL_SITE_URL
1741
+ defineSecretSet2 as defineSecretSet,
1742
+ fetchInfisicalSecrets,
1743
+ getSecretEnvironment,
1744
+ getViteDefine,
1745
+ infisicalLoader,
1746
+ loadInfisicalSecrets,
1747
+ processEnvLoader,
1748
+ viteSecretSetPlugin
1481
1749
  };