@arkenv/core 1.0.0-alpha.3 → 1.0.0-alpha.5

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/index.cjs CHANGED
@@ -2,243 +2,490 @@ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toString
2
2
  let arktype = require("arktype");
3
3
 
4
4
  //#region ../internal/scope/dist/index.js
5
- const n$1 = (0, arktype.type)(`0 <= number.integer <= 65535`), r$1 = (0, arktype.type)(`string.ip | 'localhost'`), i$1 = (0, arktype.scope)({
5
+ const n = (0, arktype.type)(`0 <= number.integer <= 65535`), r = (0, arktype.type)(`string.ip | 'localhost'`), i = (0, arktype.scope)({
6
6
  string: arktype.type.module({
7
7
  ...arktype.type.keywords.string,
8
- host: r$1
8
+ host: r
9
9
  }),
10
10
  number: arktype.type.module({
11
11
  ...arktype.type.keywords.number,
12
- port: n$1
12
+ port: n
13
13
  })
14
14
  });
15
15
 
16
+ //#endregion
17
+ //#region ../internal/utils/dist/boundary-access-error.js
18
+ /**
19
+ * `error.name` for the validation class {@link ArkEnvError}.
20
+ */
21
+ const ARKENV_ERROR_NAME = "ArkEnvError";
22
+
16
23
  //#endregion
17
24
  //#region ../internal/utils/dist/index.js
18
- const e = (e) => {
19
- if (typeof e == `number` || typeof e != `string` || !e.trim()) return e;
20
- if (e.trim() === `NaN`) return NaN;
21
- let t = Number(e);
22
- return Number.isNaN(t) ? e : t;
23
- }, t = (e) => e === `true` ? !0 : e === `false` ? !1 : e, n = (e) => {
24
- if (typeof e != `string`) return e;
25
- let t = e.trim();
26
- if (t[0] !== `{` && t[0] !== `[`) return e;
25
+ /**
26
+ * Attempt to coerce a value to a number.
27
+ *
28
+ * If the input is already a number, returns it unchanged.
29
+ * If the input is a string that can be parsed as a number, returns the parsed number.
30
+ * Otherwise, returns the original value unchanged.
31
+ *
32
+ * @internal
33
+ * @param s - The value to coerce
34
+ * @returns The coerced number or the original value
35
+ */
36
+ const coerceNumber = (s) => {
37
+ if (typeof s === "number") return s;
38
+ if (typeof s !== "string" || !s.trim()) return s;
39
+ if (s.trim() === "NaN") return NaN;
40
+ const n = Number(s);
41
+ return Number.isNaN(n) ? s : n;
42
+ };
43
+ /**
44
+ * Attempt to coerce a value to a boolean.
45
+ *
46
+ * Convert the strings "true" and "false" to their boolean equivalents.
47
+ * All other values are returned unchanged.
48
+ *
49
+ * @internal
50
+ * @param s - The value to coerce
51
+ * @returns The coerced boolean or the original value
52
+ */
53
+ const coerceBoolean = (s) => {
54
+ if (s === "true") return true;
55
+ if (s === "false") return false;
56
+ return s;
57
+ };
58
+ /**
59
+ * Attempt to parse a value as JSON.
60
+ *
61
+ * If the input is a string that starts with `{` or `[` and can be parsed as JSON,
62
+ * returns the parsed object or array. Otherwise, returns the original value unchanged.
63
+ *
64
+ * @internal
65
+ * @param s - The value to parse
66
+ * @returns The parsed JSON or the original value
67
+ */
68
+ const coerceJson = (s) => {
69
+ if (typeof s !== "string") return s;
70
+ const trimmed = s.trim();
71
+ if (trimmed[0] !== "{" && trimmed[0] !== "[") return s;
27
72
  try {
28
- return JSON.parse(t);
73
+ return JSON.parse(trimmed);
29
74
  } catch {
30
- return e;
75
+ return s;
31
76
  }
32
- }, r = (e) => {
33
- if (e instanceof Date || typeof e != `string` || !e.trim()) return e;
34
- let t = new Date(e);
35
- return Number.isNaN(t.getTime()) ? e : t;
36
- }, i = (e) => {
37
- let t = {};
38
- for (let n in e) {
39
- let r = e[n];
40
- r !== `` && (t[n] = r);
77
+ };
78
+ /**
79
+ * Attempt to coerce a value to a Date.
80
+ *
81
+ * If the input is already a Date, returns it unchanged.
82
+ * If the input is a valid date string, returns a Date object.
83
+ * Otherwise, returns the original value unchanged.
84
+ *
85
+ * @internal
86
+ * @param s - The value to coerce
87
+ * @returns The coerced Date or the original value
88
+ */
89
+ const coerceDate = (s) => {
90
+ if (s instanceof Date) return s;
91
+ if (typeof s !== "string" || !s.trim()) return s;
92
+ const d = new Date(s);
93
+ return Number.isNaN(d.getTime()) ? s : d;
94
+ };
95
+ /**
96
+ * Remove keys with empty string values from an environment record.
97
+ *
98
+ * When a key is set to `""` (e.g. `PORT=` in a `.env` file), deleting it
99
+ * allows the validator to treat it as missing so that defaults apply.
100
+ *
101
+ * @param env The environment variables record
102
+ * @returns A new record with empty string keys removed
103
+ */
104
+ const stripEmptyStrings = (env) => {
105
+ const result = {};
106
+ for (const key in env) {
107
+ const value = env[key];
108
+ if (value !== "") result[key] = value;
41
109
  }
42
- return t;
43
- }, a = `*`, o = (e, t = []) => {
44
- let n = [];
45
- if (!e || typeof e != `object` || Array.isArray(e)) return n;
46
- let r = e;
47
- if (`const` in r) {
48
- let e = typeof r.const;
49
- (e === `number` || e === `boolean`) && n.push({
50
- path: [...t],
51
- type: `primitive`
110
+ return result;
111
+ };
112
+ /**
113
+ * Find all paths in a JSON Schema that require coercion.
114
+ *
115
+ * Prioritize "number", "integer", "boolean", "array", "object", and "date" types.
116
+ *
117
+ * @param node The JSON Schema node to traverse
118
+ * @param path The current path segments in the schema tree
119
+ * @returns An array of coercion targets containing their path and type
120
+ */
121
+ const findCoercionPaths = (node, path = []) => {
122
+ const results = [];
123
+ if (!node || typeof node !== "object" || Array.isArray(node)) return results;
124
+ const n = node;
125
+ if ("const" in n) {
126
+ const t = typeof n.const;
127
+ if (t === "number" || t === "boolean") results.push({
128
+ path: [...path],
129
+ type: "primitive"
52
130
  });
53
131
  }
54
- `enum` in r && Array.isArray(r.enum) && r.enum.some((e) => typeof e == `number` || typeof e == `boolean`) && n.push({
55
- path: [...t],
56
- type: `primitive`
57
- });
58
- let i = r.type;
59
- if (i === `number` || i === `integer` || i === `boolean`) n.push({
60
- path: [...t],
61
- type: `primitive`
132
+ if ("enum" in n && Array.isArray(n.enum)) {
133
+ if (n.enum.some((v) => typeof v === "number" || typeof v === "boolean")) results.push({
134
+ path: [...path],
135
+ type: "primitive"
136
+ });
137
+ }
138
+ const type = n.type;
139
+ if (type === "number" || type === "integer" || type === "boolean") results.push({
140
+ path: [...path],
141
+ type: "primitive"
62
142
  });
63
- else if (i === `string` && `format` in r && (r.format === `date-time` || r.format === `date`)) n.push({
64
- path: [...t],
65
- type: `date`
143
+ else if (type === "string" && "format" in n && (n.format === "date-time" || n.format === "date")) results.push({
144
+ path: [...path],
145
+ type: "date"
66
146
  });
67
- else if (i === `object`) {
68
- if (r.properties && Object.keys(r.properties).length > 0) {
69
- n.push({
70
- path: [...t],
71
- type: `object`
147
+ else if (type === "object") {
148
+ if (n.properties && Object.keys(n.properties).length > 0) {
149
+ results.push({
150
+ path: [...path],
151
+ type: "object"
72
152
  });
73
- for (let e in r.properties) n.push(...o(r.properties[e], [...t, e]));
153
+ for (const key in n.properties) results.push(...findCoercionPaths(n.properties[key], [...path, key]));
74
154
  }
75
- } else i === `array` && (n.push({
76
- path: [...t],
77
- type: `array`
78
- }), r.items && (Array.isArray(r.items) ? r.items.forEach((e, r) => {
79
- n.push(...o(e, [...t, String(r)]));
80
- }) : n.push(...o(r.items, [...t, `*`]))));
81
- for (let e of [
82
- `anyOf`,
83
- `allOf`,
84
- `oneOf`
85
- ]) if (r[e] && Array.isArray(r[e])) for (let i of r[e]) n.push(...o(i, t));
86
- let a = /* @__PURE__ */ new Set();
87
- return n.filter((e) => {
88
- let t = e.path.join(`/`) + `:` + e.type;
89
- return a.has(t) ? !1 : a.add(t);
155
+ } else if (type === "array") {
156
+ results.push({
157
+ path: [...path],
158
+ type: "array"
159
+ });
160
+ if (n.items) if (Array.isArray(n.items)) n.items.forEach((item, index) => {
161
+ results.push(...findCoercionPaths(item, [...path, String(index)]));
162
+ });
163
+ else results.push(...findCoercionPaths(n.items, [...path, "*"]));
164
+ }
165
+ for (const comb of [
166
+ "anyOf",
167
+ "allOf",
168
+ "oneOf"
169
+ ]) if (n[comb] && Array.isArray(n[comb])) for (const branch of n[comb]) results.push(...findCoercionPaths(branch, path));
170
+ const seen = /* @__PURE__ */ new Set();
171
+ return results.filter((t) => {
172
+ const key = t.path.join("/") + ":" + t.type;
173
+ return seen.has(key) ? false : seen.add(key);
90
174
  });
91
- }, s = (i, a, o = {}) => {
92
- let { arrayFormat: s = `comma` } = o, c = (e) => {
93
- if (s === `json`) try {
94
- return JSON.parse(e);
175
+ };
176
+ /**
177
+ * Apply coercion to a data object based on identified paths.
178
+ *
179
+ * @param data The input environment data object to coerce
180
+ * @param targets The coercion targets mapping paths to types
181
+ * @param options The coercion options, including array parsing format
182
+ * @returns The coerced data object
183
+ */
184
+ const applyCoercion = (data, targets, options = {}) => {
185
+ const { arrayFormat = "comma" } = options;
186
+ const splitString = (val) => {
187
+ if (arrayFormat === "json") try {
188
+ return JSON.parse(val);
95
189
  } catch {
96
- return e;
190
+ return val;
97
191
  }
98
- return e.trim() ? e.split(`,`).map((e) => e.trim()) : [];
99
- }, l = (i, a) => {
100
- if (a === `array` && typeof i == `string`) return c(i);
101
- if (a === `object` && typeof i == `string`) return n(i);
102
- if (a === `date` && typeof i == `string`) return r(i);
103
- if (a === `primitive`) {
104
- if (Array.isArray(i)) return i.map((n) => {
105
- if (typeof n != `string`) return n;
106
- let r = e(n);
107
- return typeof r == `number` ? r : t(n);
192
+ return val.trim() ? val.split(",").map((s) => s.trim()) : [];
193
+ };
194
+ const coerceValue = (val, type) => {
195
+ if (type === "array" && typeof val === "string") return splitString(val);
196
+ if (type === "object" && typeof val === "string") return coerceJson(val);
197
+ if (type === "date" && typeof val === "string") return coerceDate(val);
198
+ if (type === "primitive") {
199
+ if (Array.isArray(val)) return val.map((item) => {
200
+ if (typeof item !== "string") return item;
201
+ const n = coerceNumber(item);
202
+ return typeof n === "number" ? n : coerceBoolean(item);
108
203
  });
109
- if (typeof i != `string`) return i;
110
- let n = e(i);
111
- return typeof n == `number` ? n : t(i);
204
+ if (typeof val !== "string") return val;
205
+ const n = coerceNumber(val);
206
+ return typeof n === "number" ? n : coerceBoolean(val);
112
207
  }
113
- return i;
208
+ return val;
114
209
  };
115
- if (typeof i != `object` || !i) {
116
- let e = a.find((e) => e.path.length === 0);
117
- return e ? l(i, e.type) : i;
210
+ if (typeof data !== "object" || data === null) {
211
+ const root = targets.find((t) => t.path.length === 0);
212
+ if (root) return coerceValue(data, root.type);
213
+ return data;
118
214
  }
119
- let u = [...a].sort((e, t) => e.path.length - t.path.length), d = (e, t, n) => {
120
- if (t.length === 0) return n(e);
121
- let [r, ...i] = t;
122
- if (r === `*`) {
123
- if (Array.isArray(e)) {
124
- let t = !1, r = e.map((e) => {
125
- let r = d(e, i, n);
126
- return r !== e && (t = !0), r;
215
+ const sorted = [...targets].sort((a, b) => a.path.length - b.path.length);
216
+ const updateAtPath = (current, path, fn) => {
217
+ if (path.length === 0) return fn(current);
218
+ const [key, ...rest] = path;
219
+ if (key === "*") {
220
+ if (Array.isArray(current)) {
221
+ let changed = false;
222
+ const nextArr = current.map((item) => {
223
+ const nextVal = updateAtPath(item, rest, fn);
224
+ if (nextVal !== item) changed = true;
225
+ return nextVal;
127
226
  });
128
- return t ? r : e;
227
+ return changed ? nextArr : current;
129
228
  }
130
- return e;
229
+ return current;
131
230
  }
132
- if (!e || typeof e != `object`) return e;
133
- if (Array.isArray(e)) {
134
- let t = Number(r);
135
- if (!Number.isNaN(t) && t >= 0 && t < e.length) {
136
- let r = d(e[t], i, n);
137
- if (r !== e[t]) {
138
- let n = [...e];
139
- return n[t] = r, n;
231
+ if (!current || typeof current !== "object") return current;
232
+ if (Array.isArray(current)) {
233
+ const index = Number(key);
234
+ if (!Number.isNaN(index) && index >= 0 && index < current.length) {
235
+ const nextVal = updateAtPath(current[index], rest, fn);
236
+ if (nextVal !== current[index]) {
237
+ const copy = [...current];
238
+ copy[index] = nextVal;
239
+ return copy;
140
240
  }
141
241
  }
142
- return e;
242
+ return current;
143
243
  }
144
- if (Object.hasOwn(e, r)) {
145
- let t = d(e[r], i, n);
146
- if (t !== e[r]) return {
147
- ...e,
148
- [r]: t
244
+ if (Object.hasOwn(current, key)) {
245
+ const nextVal = updateAtPath(current[key], rest, fn);
246
+ if (nextVal !== current[key]) return {
247
+ ...current,
248
+ [key]: nextVal
149
249
  };
150
250
  }
151
- return e;
152
- }, f = i;
153
- for (let e of u) e.path.length > 0 && (f = d(f, e.path, (t) => l(t, e.type)));
154
- return f;
251
+ return current;
252
+ };
253
+ let result = data;
254
+ for (const t of sorted) if (t.path.length > 0) result = updateAtPath(result, t.path, (val) => coerceValue(val, t.type));
255
+ return result;
155
256
  };
156
- function c(e, t, n, r) {
157
- let a = t ? i(e) : e, c = { ...a }, l = [];
158
- if (r) {
159
- let e = r();
160
- l.push(...e.missingKeys || []), e.hasSchema && (c = s(c, o(e.schema), { arrayFormat: n }));
257
+ /**
258
+ * Prepare an environment record by optionally stripping empty strings and applying coercion.
259
+ *
260
+ * @param env The raw environment variables
261
+ * @param emptyAsUndefined Whether to strip empty string values before processing
262
+ * @param arrayFormat The format to use for array coercion
263
+ * @param getSchema Optional callback that returns a JSON Schema and whether it exists,
264
+ * used to determine coercion targets. When omitted, no coercion is performed.
265
+ * @returns The processed environment, the coerced environment, and any missing schema keys
266
+ */
267
+ function coerceEnvironment(env, emptyAsUndefined, arrayFormat, getSchema) {
268
+ const processedEnv = emptyAsUndefined ? stripEmptyStrings(env) : env;
269
+ let coercedEnv = { ...processedEnv };
270
+ const missingKeys = [];
271
+ if (getSchema) {
272
+ const result = getSchema();
273
+ missingKeys.push(...result.missingKeys || []);
274
+ if (result.hasSchema) coercedEnv = applyCoercion(coercedEnv, findCoercionPaths(result.schema), { arrayFormat });
161
275
  }
162
276
  return {
163
- processedEnv: a,
164
- coercedEnv: c,
165
- missingKeys: l
277
+ processedEnv,
278
+ coercedEnv,
279
+ missingKeys
166
280
  };
167
281
  }
168
- const l = (e, t = 2, { dontDetectNewlines: n = !1 } = {}) => n ? `${` `.repeat(t)}${e}` : e.split(`
169
- `).map((e) => `${` `.repeat(t)}${e}`).join(`
170
- `), u = {
171
- red: `\x1B[31m`,
172
- yellow: `\x1B[33m`,
173
- cyan: `\x1B[36m`,
174
- reset: `\x1B[0m`
175
- }, d = () => typeof process < `u` && process.versions != null && process.versions.node != null, f = () => !!(!d() || process.env.NO_COLOR !== void 0 || process.env.CI !== void 0 || process.stdout && !process.stdout.isTTY), p = (e, t) => d() && !f() ? `${u[e]}${t}${u.reset}` : t;
176
- function m(e) {
177
- return e.map((e) => `${p(`yellow`, e.path)} ${e.message.trimStart()}`).join(`
178
- `);
282
+ /**
283
+ * Indent a string by a given amount
284
+ * @param str - The string to indent
285
+ * @param amt - The amount to indent by, defaults to 2
286
+ * @param options - {@link IndentOptions}
287
+ * @returns The indented string
288
+ */
289
+ const indent = (str, amt = 2, { dontDetectNewlines = false } = {}) => {
290
+ if (!dontDetectNewlines) return str.split("\n").map((line) => `${" ".repeat(amt)}${line}`).join("\n");
291
+ return `${" ".repeat(amt)}${str}`;
292
+ };
293
+ /**
294
+ * Cross-platform text styling utility
295
+ * Uses ANSI colors in Node environments, plain text in browsers
296
+ * Respects NO_COLOR, CI environment variables, and TTY detection
297
+ */
298
+ const colors = {
299
+ red: "\x1B[31m",
300
+ yellow: "\x1B[33m",
301
+ cyan: "\x1B[36m",
302
+ reset: "\x1B[0m"
303
+ };
304
+ /**
305
+ * Check if we're in a Node environment (not browser)
306
+ * Checked dynamically to allow for testing with mocked globals
307
+ */
308
+ const isNode = () => typeof process !== "undefined" && process.versions != null && process.versions.node != null;
309
+ /**
310
+ * Check if colors should be disabled based on environment
311
+ * Respects NO_COLOR, CI environment variables, and TTY detection
312
+ */
313
+ const shouldDisableColors = () => {
314
+ if (!isNode()) return true;
315
+ if (process.env.NO_COLOR !== void 0) return true;
316
+ if (process.env.CI !== void 0) return true;
317
+ if (process.stdout && !process.stdout.isTTY) return true;
318
+ return false;
319
+ };
320
+ /**
321
+ * Style text with color. Uses ANSI codes in Node, plain text in browsers.
322
+ * @param color - The color to apply
323
+ * @param text - The text to style
324
+ * @returns Styled text in Node (if colors enabled), plain text otherwise
325
+ */
326
+ const styleText = (color, text) => {
327
+ if (isNode() && !shouldDisableColors()) return `${colors[color]}${text}${colors.reset}`;
328
+ return text;
329
+ };
330
+ /**
331
+ * Format a list of normalized environment issues into a single styled string.
332
+ *
333
+ * @param issues - The array of normalized issues to format
334
+ * @returns The formatted and styled error report string
335
+ */
336
+ function formatIssues(issues) {
337
+ return issues.map((issue) => {
338
+ return `${styleText("yellow", issue.path)} ${issue.message.trimStart()}`;
339
+ }).join("\n");
179
340
  }
180
- var h = class extends Error {
181
- constructor(e, t = `Errors found while validating environment variables`) {
182
- let n = m(e);
183
- super(`${p(`red`, t)}\n${l(n)}\n`), this.name = `ArkEnvError`, this.issues = e;
341
+ /**
342
+ * Error thrown when environment variable validation fails.
343
+ *
344
+ * This error extends the native `Error` class and provides formatted error messages
345
+ * that clearly indicate which environment variables are invalid and why.
346
+ *
347
+ * @example
348
+ * ```ts
349
+ * try {
350
+ * const env = arkenv({
351
+ * PORT: 'number.port',
352
+ * HOST: 'string.host',
353
+ * });
354
+ * } catch (error) {
355
+ * if (error instanceof ArkEnvError) {
356
+ * console.error('Environment validation failed:', error.message);
357
+ * }
358
+ * }
359
+ * ```
360
+ */
361
+ var ArkEnvError = class extends Error {
362
+ constructor(issues, message = "Errors found while validating environment variables") {
363
+ const formattedIssues = formatIssues(issues);
364
+ super(`${styleText("red", message)}\n${indent(formattedIssues)}\n`);
365
+ this.name = ARKENV_ERROR_NAME;
366
+ this.issues = issues;
184
367
  }
185
368
  };
186
- Object.defineProperty(h, `name`, { value: `ArkEnvError` });
187
- const y = /secret|(_|^)key(_|$)|token|(_|^)password(_|$)|(_|^)pass(_|$)|(_|^)auth(_|$)|jwt|cert|credential|database_url|db_url/i;
188
- function b(e) {
189
- if (e !== void 0) return e;
190
- if (typeof process > `u`) return !1;
191
- let t = process.env.ARKENV_DEBUG_SECRETS;
192
- return t === `true` || t === `1`;
369
+ Object.defineProperty(ArkEnvError, "name", { value: ARKENV_ERROR_NAME });
370
+ /**
371
+ * Regex pattern matching sensitive environment variable names.
372
+ *
373
+ * Matches keywords commonly associated with secrets (e.g. secret, key, token,
374
+ * password, pass, auth, jwt, cert, credential, db_url). Excludes public keys
375
+ * via the `shouldRedact` helper.
376
+ *
377
+ * @see {@link shouldRedact}
378
+ */
379
+ const SENSITIVE_PATTERN = /secret|(_|^)key(_|$)|token|(_|^)password(_|$)|(_|^)pass(_|$)|(_|^)auth(_|$)|jwt|cert|credential|database_url|db_url/i;
380
+ /**
381
+ * Check if debug secrets mode is enabled.
382
+ *
383
+ * Debug secrets mode can be enabled programmatically via the `debugSecrets` config option,
384
+ * or globally by setting the `ARKENV_DEBUG_SECRETS` environment variable to `"true"` or `"1"`.
385
+ *
386
+ * @param configSecrets Programmatic override option for debugging secrets
387
+ * @returns A boolean indicating if debug secrets mode is active
388
+ */
389
+ function isDebugSecrets(configSecrets) {
390
+ if (configSecrets !== void 0) return configSecrets;
391
+ if (typeof process === "undefined") return false;
392
+ const val = process.env.ARKENV_DEBUG_SECRETS;
393
+ return val === "true" || val === "1";
193
394
  }
194
- function x(e) {
195
- return y.test(e) && !/public/i.test(e);
395
+ /**
396
+ * Determine if an environment variable path matches sensitive keyword patterns.
397
+ *
398
+ * By default, environment variables that contain sensitive keywords (e.g. 'secret', 'key',
399
+ * 'token', 'password', 'auth', 'jwt', 'cert', 'credential', 'db_url') are flagged for redaction,
400
+ * unless they are explicitly marked as public (e.g., matching 'public').
401
+ *
402
+ * Redaction prevents sensitive values from being logged or printed to the terminal
403
+ * when environment validation fails.
404
+ *
405
+ * @param path The environment variable name/path under validation
406
+ * @returns A boolean indicating if the path is sensitive and should be redacted
407
+ */
408
+ function shouldRedact(path) {
409
+ return SENSITIVE_PATTERN.test(path) && !/public/i.test(path);
196
410
  }
197
- function E(e) {
411
+ /**
412
+ * Execute a parser function and return a SafeArkEnvResult.
413
+ *
414
+ * @param parseFn The function that parses the environment variables and might throw an ArkEnvError
415
+ * @returns A SafeArkEnvResult containing either the parsed data or the caught ArkEnvError
416
+ * @internal
417
+ */
418
+ function safeExecute(parseFn) {
198
419
  try {
199
420
  return {
200
- success: !0,
201
- data: e()
421
+ success: true,
422
+ data: parseFn()
202
423
  };
203
- } catch (e) {
204
- if (e instanceof h) return {
205
- success: !1,
206
- issues: e.issues
424
+ } catch (error) {
425
+ if (error instanceof ArkEnvError) return {
426
+ success: false,
427
+ issues: error.issues
207
428
  };
208
- throw e;
429
+ throw error;
209
430
  }
210
431
  }
211
- function D(e, t, n, r, i, a) {
212
- let o = {
213
- path: e,
214
- message: t,
215
- code: n,
216
- meta: r ?? {}
432
+ /**
433
+ * Build a normalized {@link EnvIssue}.
434
+ *
435
+ * @param path The dot-separated property path/name of the environment variable
436
+ * @param message The descriptive, user-friendly error message
437
+ * @param code The normalized classification code for the issue
438
+ * @param meta Additional validation metadata and engine codes
439
+ * @param expected The expected type or value shape description
440
+ * @param received The raw value received (redacted in string formatting if sensitive)
441
+ * @returns A fully populated EnvIssue
442
+ * @internal
443
+ */
444
+ function buildEnvIssue(path, message, code, meta, expected, received) {
445
+ const issue = {
446
+ path,
447
+ message,
448
+ code,
449
+ meta: meta ?? {}
217
450
  };
218
- return i && (o.expected = i), a !== void 0 && (o.received = a), o;
451
+ if (expected) issue.expected = expected;
452
+ if (received !== void 0) issue.received = received;
453
+ return issue;
219
454
  }
220
- function P(e) {
221
- if (!e || typeof e != `object` && typeof e != `function`) return [];
222
- if (e.json && typeof e.json == `object` && e.json.domain === `object`) {
223
- let t = [];
224
- if (Array.isArray(e.json.required)) for (let n of e.json.required) n && typeof n == `object` && `key` in n && t.push(n.key);
225
- if (Array.isArray(e.json.optional)) for (let n of e.json.optional) n && typeof n == `object` && `key` in n && t.push(n.key);
226
- return t;
455
+ /**
456
+ * Extract the keys from a schema definition.
457
+ * Supports plain objects, ArkType schemas, and Standard Schema validators.
458
+ *
459
+ * @param schema The schema definition to extract keys from
460
+ * @returns An array of extracted key names
461
+ */
462
+ function getSchemaKeys(schema) {
463
+ if (!schema || typeof schema !== "object" && typeof schema !== "function") return [];
464
+ if (schema.json && typeof schema.json === "object" && schema.json.domain === "object") {
465
+ const keys = [];
466
+ if (Array.isArray(schema.json.required)) {
467
+ for (const r of schema.json.required) if (r && typeof r === "object" && "key" in r) keys.push(r.key);
468
+ }
469
+ if (Array.isArray(schema.json.optional)) {
470
+ for (const o of schema.json.optional) if (o && typeof o === "object" && "key" in o) keys.push(o.key);
471
+ }
472
+ return keys;
227
473
  }
228
- let t = e[`~standard`], n = typeof t?.jsonSchema?.input == `function` && t.jsonSchema.input || typeof e.jsonSchema?.input == `function` && e.jsonSchema.input;
229
- if (n) try {
230
- let e = n({ target: `draft-07` });
231
- if (e && typeof e == `object` && e.properties) return Object.keys(e.properties);
474
+ const std = schema["~standard"];
475
+ const jsonSchemaInput = typeof std?.jsonSchema?.input === "function" && std.jsonSchema.input || typeof schema.jsonSchema?.input === "function" && schema.jsonSchema.input;
476
+ if (jsonSchemaInput) try {
477
+ const json = jsonSchemaInput({ target: "draft-07" });
478
+ if (json && typeof json === "object" && json.properties) return Object.keys(json.properties);
232
479
  } catch {}
233
- if (typeof e.toJSONSchema == `function`) try {
234
- let t = e.toJSONSchema();
235
- if (t && typeof t == `object` && t.properties) return Object.keys(t.properties);
480
+ if (typeof schema.toJSONSchema === "function") try {
481
+ const json = schema.toJSONSchema();
482
+ if (json && typeof json === "object" && json.properties) return Object.keys(json.properties);
236
483
  } catch {}
237
- if (typeof e.toStandardJSONSchema?.v1 == `function`) try {
238
- let t = e.toStandardJSONSchema.v1();
239
- if (t && typeof t == `object` && t.properties) return Object.keys(t.properties);
484
+ if (typeof schema.toStandardJSONSchema?.v1 === "function") try {
485
+ const json = schema.toStandardJSONSchema.v1();
486
+ if (json && typeof json === "object" && json.properties) return Object.keys(json.properties);
240
487
  } catch {}
241
- return Object.keys(e);
488
+ return Object.keys(schema);
242
489
  }
243
490
 
244
491
  //#endregion
@@ -296,9 +543,9 @@ function redactMessageWasValue(message, path, debugSecrets) {
296
543
  const valueMatch = message.match(/\(was (.*)\)/);
297
544
  if (!valueMatch?.[1]) return message;
298
545
  const value = valueMatch[1];
299
- const displayedValue = !b(debugSecrets) && x(path) ? "[REDACTED]" : value;
546
+ const displayedValue = !isDebugSecrets(debugSecrets) && shouldRedact(path) ? "[REDACTED]" : value;
300
547
  if (displayedValue.includes("\x1B[")) return message;
301
- return message.replace(`(was ${value})`, `(was ${p("cyan", displayedValue)})`);
548
+ return message.replace(`(was ${value})`, `(was ${styleText("cyan", displayedValue)})`);
302
549
  }
303
550
  /**
304
551
  * Convert ArkType's `ArkErrors` (keyed by path) into a flat `EnvIssue[]`
@@ -323,7 +570,7 @@ function arkErrorsToIssues(errors, config) {
323
570
  message = redactMessageWasValue(message, path, config?.debugSecrets);
324
571
  const code = mapArkTypeCode(error.code);
325
572
  const meta = { ...getArkTypeMeta(error) };
326
- return D(path, message, code, meta, error.expected, error.code === "required" ? void 0 : error.data);
573
+ return buildEnvIssue(path, message, code, meta, error.expected, error.code === "required" ? void 0 : error.data);
327
574
  });
328
575
  }
329
576
  /**
@@ -349,22 +596,22 @@ function arkErrorsToIssues(errors, config) {
349
596
  */
350
597
  function parse(def, config) {
351
598
  const { env = process.env, coerce: shouldCoerce = true, onUndeclaredKey = "delete", arrayFormat = "comma", emptyAsUndefined = false } = config;
352
- const schemaWithKeys = (typeof def === "function" && "assert" in def ? def : i$1.type.raw(def)).onUndeclaredKey(onUndeclaredKey);
353
- const { coercedEnv } = c(env, emptyAsUndefined, arrayFormat, shouldCoerce ? () => {
599
+ const schemaWithKeys = (typeof def === "function" && "assert" in def ? def : i.type.raw(def)).onUndeclaredKey(onUndeclaredKey);
600
+ const { coercedEnv } = coerceEnvironment(env, emptyAsUndefined, arrayFormat, shouldCoerce ? () => {
354
601
  return {
355
602
  schema: schemaWithKeys.in.toJsonSchema({ fallback: (ctx) => ctx.base }),
356
603
  hasSchema: true
357
604
  };
358
605
  } : void 0);
359
606
  const validatedEnv = schemaWithKeys(coercedEnv);
360
- if (validatedEnv instanceof arktype.ArkErrors) throw new h(arkErrorsToIssues(validatedEnv, config));
607
+ if (validatedEnv instanceof arktype.ArkErrors) throw new ArkEnvError(arkErrorsToIssues(validatedEnv, config));
361
608
  return validatedEnv;
362
609
  }
363
610
 
364
611
  //#endregion
365
612
  //#region src/arkenv.ts
366
613
  function arkenv(def, config = {}) {
367
- if (config.safe) return E(() => parse(def, config));
614
+ if (config.safe) return safeExecute(() => parse(def, config));
368
615
  return parse(def, config);
369
616
  }
370
617
 
@@ -379,20 +626,15 @@ function arkenv(def, config = {}) {
379
626
  * See ArkType's docs for the full API:
380
627
  * https://arktype.io/docs/type-api
381
628
  */
382
- const type = i$1.type;
383
- /**
384
- * ArkEnv's main export, an alias for {@link arkenv}
385
- *
386
- * {@link https://arkenv.js.org | ArkEnv} is a typesafe environment variables validator from editor to runtime.
387
- */
629
+ const type = i.type;
388
630
  var src_default = arkenv;
389
631
 
390
632
  //#endregion
391
- exports.ArkEnvError = h;
633
+ exports.ArkEnvError = ArkEnvError;
392
634
  exports.arkenv = arkenv;
393
635
  exports.default = src_default;
394
- exports.formatIssues = m;
395
- exports.getSchemaKeys = P;
636
+ exports.formatIssues = formatIssues;
637
+ exports.getSchemaKeys = getSchemaKeys;
396
638
  exports.type = type;
397
639
 
398
640
  // CJS Interop Shim