@optique/core 1.0.0-dev.788 → 1.0.0-dev.886

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/facade.cjs CHANGED
@@ -132,16 +132,143 @@ function finalizeParsedForContext(context, parsed) {
132
132
  if (parsed !== void 0 || !Reflect.has(context, phase1ConfigAnnotationsKey)) return parsed;
133
133
  return { [phase2UndefinedParsedValueKey]: true };
134
134
  }
135
+ const SANITIZE_FAILED = Symbol("sanitizeFailed");
136
+ const activeSanitizations = /* @__PURE__ */ new WeakMap();
137
+ function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
138
+ let active = activeSanitizations.get(target);
139
+ if (active != null) active.count++;
140
+ else {
141
+ const saved = /* @__PURE__ */ new Map();
142
+ const sanitizedValues = /* @__PURE__ */ new Map();
143
+ for (const key of Reflect.ownKeys(target)) {
144
+ const desc = Object.getOwnPropertyDescriptor(target, key);
145
+ if (desc != null && "value" in desc) {
146
+ let stripped;
147
+ try {
148
+ stripped = strip(desc.value, seen);
149
+ } catch {
150
+ for (const [k, d] of saved) try {
151
+ Object.defineProperty(target, k, d);
152
+ } catch {}
153
+ return SANITIZE_FAILED;
154
+ }
155
+ if (stripped !== desc.value) try {
156
+ Object.defineProperty(target, key, {
157
+ ...desc,
158
+ value: stripped
159
+ });
160
+ saved.set(key, desc);
161
+ sanitizedValues.set(key, stripped);
162
+ } catch {
163
+ for (const [k, d] of saved) try {
164
+ Object.defineProperty(target, k, d);
165
+ } catch {}
166
+ return SANITIZE_FAILED;
167
+ }
168
+ }
169
+ }
170
+ active = {
171
+ saved,
172
+ sanitizedValues,
173
+ count: 1
174
+ };
175
+ activeSanitizations.set(target, active);
176
+ }
177
+ function release() {
178
+ active.count--;
179
+ if (active.count === 0) {
180
+ activeSanitizations.delete(target);
181
+ for (const [key, desc] of active.saved) try {
182
+ const current = Object.getOwnPropertyDescriptor(target, key);
183
+ if (current == null) continue;
184
+ if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
185
+ Object.defineProperty(target, key, desc);
186
+ } catch {}
187
+ }
188
+ }
189
+ let result;
190
+ try {
191
+ result = fn.apply(target, args);
192
+ } catch (e) {
193
+ release();
194
+ throw e;
195
+ }
196
+ if (result instanceof Promise) return result.then((v) => {
197
+ release();
198
+ return strip(v, seen);
199
+ }, (e) => {
200
+ release();
201
+ throw e;
202
+ });
203
+ release();
204
+ return strip(result, seen);
205
+ }
206
+ function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
207
+ const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
208
+ if (result !== SANITIZE_FAILED) return result;
209
+ const fallback = fn.apply(proxy, args);
210
+ if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
211
+ return strip(fallback, seen);
212
+ }
135
213
  function createSanitizedNonPlainContextView(value$1, seen) {
214
+ const methodCache = /* @__PURE__ */ new Map();
136
215
  const proxy = new Proxy(value$1, {
137
216
  get(target, key, receiver) {
138
217
  const descriptor = Object.getOwnPropertyDescriptor(target, key);
139
- if (descriptor != null && "value" in descriptor) return stripDeferredPromptValuesForContexts(descriptor.value, seen);
140
- return Reflect.get(target, key, receiver);
218
+ if (descriptor != null && "value" in descriptor) {
219
+ if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
220
+ const val = stripDeferredPromptValuesForContexts(descriptor.value, seen);
221
+ if (typeof val === "function") {
222
+ if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
223
+ const cached = methodCache.get(key);
224
+ if (cached != null && cached.fn === val) return cached.wrapper;
225
+ const wrapper = function(...args) {
226
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(val.apply(this, args), seen);
227
+ return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
228
+ };
229
+ methodCache.set(key, {
230
+ fn: val,
231
+ wrapper
232
+ });
233
+ return wrapper;
234
+ }
235
+ return val;
236
+ }
237
+ let isAccessor = false;
238
+ for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
239
+ const d = Object.getOwnPropertyDescriptor(proto, key);
240
+ if (d != null) {
241
+ isAccessor = "get" in d;
242
+ break;
243
+ }
244
+ }
245
+ const result = Reflect.get(target, key, receiver);
246
+ if (typeof result === "function") {
247
+ if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
248
+ if (!isAccessor) {
249
+ const cached = methodCache.get(key);
250
+ if (cached != null && cached.fn === result) return cached.wrapper;
251
+ const wrapper = function(...args) {
252
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
253
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
254
+ };
255
+ methodCache.set(key, {
256
+ fn: result,
257
+ wrapper
258
+ });
259
+ return wrapper;
260
+ }
261
+ return function(...args) {
262
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
263
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
264
+ };
265
+ }
266
+ return stripDeferredPromptValuesForContexts(result, seen);
141
267
  },
142
268
  getOwnPropertyDescriptor(target, key) {
143
269
  const descriptor = Object.getOwnPropertyDescriptor(target, key);
144
270
  if (descriptor == null || !("value" in descriptor)) return descriptor;
271
+ if (!descriptor.configurable && !descriptor.writable) return descriptor;
145
272
  return {
146
273
  ...descriptor,
147
274
  value: stripDeferredPromptValuesForContexts(descriptor.value, seen)
package/dist/facade.js CHANGED
@@ -132,16 +132,143 @@ function finalizeParsedForContext(context, parsed) {
132
132
  if (parsed !== void 0 || !Reflect.has(context, phase1ConfigAnnotationsKey)) return parsed;
133
133
  return { [phase2UndefinedParsedValueKey]: true };
134
134
  }
135
+ const SANITIZE_FAILED = Symbol("sanitizeFailed");
136
+ const activeSanitizations = /* @__PURE__ */ new WeakMap();
137
+ function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
138
+ let active = activeSanitizations.get(target);
139
+ if (active != null) active.count++;
140
+ else {
141
+ const saved = /* @__PURE__ */ new Map();
142
+ const sanitizedValues = /* @__PURE__ */ new Map();
143
+ for (const key of Reflect.ownKeys(target)) {
144
+ const desc = Object.getOwnPropertyDescriptor(target, key);
145
+ if (desc != null && "value" in desc) {
146
+ let stripped;
147
+ try {
148
+ stripped = strip(desc.value, seen);
149
+ } catch {
150
+ for (const [k, d] of saved) try {
151
+ Object.defineProperty(target, k, d);
152
+ } catch {}
153
+ return SANITIZE_FAILED;
154
+ }
155
+ if (stripped !== desc.value) try {
156
+ Object.defineProperty(target, key, {
157
+ ...desc,
158
+ value: stripped
159
+ });
160
+ saved.set(key, desc);
161
+ sanitizedValues.set(key, stripped);
162
+ } catch {
163
+ for (const [k, d] of saved) try {
164
+ Object.defineProperty(target, k, d);
165
+ } catch {}
166
+ return SANITIZE_FAILED;
167
+ }
168
+ }
169
+ }
170
+ active = {
171
+ saved,
172
+ sanitizedValues,
173
+ count: 1
174
+ };
175
+ activeSanitizations.set(target, active);
176
+ }
177
+ function release() {
178
+ active.count--;
179
+ if (active.count === 0) {
180
+ activeSanitizations.delete(target);
181
+ for (const [key, desc] of active.saved) try {
182
+ const current = Object.getOwnPropertyDescriptor(target, key);
183
+ if (current == null) continue;
184
+ if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
185
+ Object.defineProperty(target, key, desc);
186
+ } catch {}
187
+ }
188
+ }
189
+ let result;
190
+ try {
191
+ result = fn.apply(target, args);
192
+ } catch (e) {
193
+ release();
194
+ throw e;
195
+ }
196
+ if (result instanceof Promise) return result.then((v) => {
197
+ release();
198
+ return strip(v, seen);
199
+ }, (e) => {
200
+ release();
201
+ throw e;
202
+ });
203
+ release();
204
+ return strip(result, seen);
205
+ }
206
+ function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
207
+ const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
208
+ if (result !== SANITIZE_FAILED) return result;
209
+ const fallback = fn.apply(proxy, args);
210
+ if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
211
+ return strip(fallback, seen);
212
+ }
135
213
  function createSanitizedNonPlainContextView(value$1, seen) {
214
+ const methodCache = /* @__PURE__ */ new Map();
136
215
  const proxy = new Proxy(value$1, {
137
216
  get(target, key, receiver) {
138
217
  const descriptor = Object.getOwnPropertyDescriptor(target, key);
139
- if (descriptor != null && "value" in descriptor) return stripDeferredPromptValuesForContexts(descriptor.value, seen);
140
- return Reflect.get(target, key, receiver);
218
+ if (descriptor != null && "value" in descriptor) {
219
+ if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
220
+ const val = stripDeferredPromptValuesForContexts(descriptor.value, seen);
221
+ if (typeof val === "function") {
222
+ if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
223
+ const cached = methodCache.get(key);
224
+ if (cached != null && cached.fn === val) return cached.wrapper;
225
+ const wrapper = function(...args) {
226
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(val.apply(this, args), seen);
227
+ return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
228
+ };
229
+ methodCache.set(key, {
230
+ fn: val,
231
+ wrapper
232
+ });
233
+ return wrapper;
234
+ }
235
+ return val;
236
+ }
237
+ let isAccessor = false;
238
+ for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
239
+ const d = Object.getOwnPropertyDescriptor(proto, key);
240
+ if (d != null) {
241
+ isAccessor = "get" in d;
242
+ break;
243
+ }
244
+ }
245
+ const result = Reflect.get(target, key, receiver);
246
+ if (typeof result === "function") {
247
+ if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
248
+ if (!isAccessor) {
249
+ const cached = methodCache.get(key);
250
+ if (cached != null && cached.fn === result) return cached.wrapper;
251
+ const wrapper = function(...args) {
252
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
253
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
254
+ };
255
+ methodCache.set(key, {
256
+ fn: result,
257
+ wrapper
258
+ });
259
+ return wrapper;
260
+ }
261
+ return function(...args) {
262
+ if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
263
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
264
+ };
265
+ }
266
+ return stripDeferredPromptValuesForContexts(result, seen);
141
267
  },
142
268
  getOwnPropertyDescriptor(target, key) {
143
269
  const descriptor = Object.getOwnPropertyDescriptor(target, key);
144
270
  if (descriptor == null || !("value" in descriptor)) return descriptor;
271
+ if (!descriptor.configurable && !descriptor.writable) return descriptor;
145
272
  return {
146
273
  ...descriptor,
147
274
  value: stripDeferredPromptValuesForContexts(descriptor.value, seen)
@@ -15,10 +15,14 @@ function isValueParser(object) {
15
15
  */
16
16
  function choice(choices, options = {}) {
17
17
  if (choices.length < 1) throw new TypeError("Expected at least one choice, but got an empty array.");
18
+ if (choices.some((c) => c === "")) throw new TypeError("Empty strings are not allowed as choices.");
19
+ for (const c of choices) if (typeof c !== "string" && typeof c !== "number") throw new TypeError(`Expected every choice to be a string or number, but got ${typeof c}.`);
20
+ const isNumber = typeof choices[0] === "number";
21
+ for (const c of choices) if (isNumber ? typeof c !== "number" : typeof c !== "string") throw new TypeError(`Expected every choice to be the same type, but got both ${isNumber ? "number" : "string"} and ${typeof c}.`);
22
+ if (isNumber && choices.some((v) => Number.isNaN(v))) throw new TypeError("NaN is not allowed in number choices.");
18
23
  const metavar = options.metavar ?? "TYPE";
19
24
  require_nonempty.ensureNonEmptyString(metavar);
20
- const isNumberChoice = choices.length > 0 && typeof choices[0] === "number";
21
- if (isNumberChoice) {
25
+ if (isNumber) {
22
26
  const numberChoices = (() => {
23
27
  const seen = /* @__PURE__ */ new Set();
24
28
  let hasPositiveZero = false;
@@ -39,17 +43,16 @@ function choice(choices, options = {}) {
39
43
  }
40
44
  return result;
41
45
  })();
42
- const numberOptions = options;
43
- const hasNaN = numberChoices.some((v) => Number.isNaN(v));
44
- const validNumberChoices = hasNaN ? numberChoices.filter((v) => !Number.isNaN(v)) : numberChoices;
46
+ const numberInvalidChoice = options.errors?.invalidChoice;
45
47
  const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
48
+ const frozenNumberChoices = Object.freeze(numberChoices);
46
49
  return {
47
50
  $mode: "sync",
48
51
  metavar,
49
- choices: hasNaN ? validNumberChoices : numberChoices,
52
+ choices: frozenNumberChoices,
50
53
  parse(input) {
51
54
  const index = numberStrings.indexOf(input);
52
- if (index >= 0 && !Number.isNaN(numberChoices[index])) return {
55
+ if (index >= 0) return {
53
56
  success: true,
54
57
  value: numberChoices[index]
55
58
  };
@@ -94,7 +97,7 @@ function choice(choices, options = {}) {
94
97
  }
95
98
  return {
96
99
  success: false,
97
- error: formatNumberChoiceError(input, validNumberChoices, numberChoices, numberOptions)
100
+ error: formatNumberChoiceError(input, numberChoices, numberChoices, numberInvalidChoice)
98
101
  };
99
102
  },
100
103
  format(value) {
@@ -108,10 +111,12 @@ function choice(choices, options = {}) {
108
111
  }
109
112
  };
110
113
  }
111
- const stringChoices = [...new Set(choices)];
114
+ const stringChoices = Object.freeze([...new Set(choices)]);
112
115
  const stringOptions = options;
113
- const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
114
- if (stringOptions.caseInsensitive) {
116
+ if (stringOptions.caseInsensitive !== void 0 && typeof stringOptions.caseInsensitive !== "boolean") throw new TypeError(`Expected caseInsensitive to be a boolean, but got ${typeof stringOptions.caseInsensitive}: ${String(stringOptions.caseInsensitive)}.`);
117
+ const caseInsensitive = stringOptions.caseInsensitive ?? false;
118
+ const normalizedValues = caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
119
+ if (caseInsensitive) {
115
120
  const seen = /* @__PURE__ */ new Map();
116
121
  for (let i = 0; i < stringChoices.length; i++) {
117
122
  const nv = normalizedValues[i];
@@ -121,16 +126,17 @@ function choice(choices, options = {}) {
121
126
  seen.set(nv, original);
122
127
  }
123
128
  }
129
+ const stringInvalidChoice = stringOptions.errors?.invalidChoice;
124
130
  return {
125
131
  $mode: "sync",
126
132
  metavar,
127
133
  choices: stringChoices,
128
134
  parse(input) {
129
- const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
135
+ const normalizedInput = caseInsensitive ? input.toLowerCase() : input;
130
136
  const index = normalizedValues.indexOf(normalizedInput);
131
137
  if (index < 0) return {
132
138
  success: false,
133
- error: formatStringChoiceError(input, stringChoices, stringOptions)
139
+ error: formatStringChoiceError(input, stringChoices, stringInvalidChoice)
134
140
  };
135
141
  return {
136
142
  success: true,
@@ -141,9 +147,9 @@ function choice(choices, options = {}) {
141
147
  return String(value);
142
148
  },
143
149
  suggest(prefix) {
144
- const normalizedPrefix = stringOptions.caseInsensitive ? prefix.toLowerCase() : prefix;
150
+ const normalizedPrefix = caseInsensitive ? prefix.toLowerCase() : prefix;
145
151
  return stringChoices.filter((value) => {
146
- const normalizedValue = stringOptions.caseInsensitive ? value.toLowerCase() : value;
152
+ const normalizedValue = caseInsensitive ? value.toLowerCase() : value;
147
153
  return normalizedValue.startsWith(normalizedPrefix);
148
154
  }).map((value) => ({
149
155
  kind: "literal",
@@ -203,15 +209,15 @@ function normalizeDecimal(s) {
203
209
  /**
204
210
  * Formats error message for string choice parser.
205
211
  */
206
- function formatStringChoiceError(input, choices, options) {
207
- if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
212
+ function formatStringChoiceError(input, choices, invalidChoice) {
213
+ if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, choices) : invalidChoice;
208
214
  return formatDefaultChoiceError(input, choices);
209
215
  }
210
216
  /**
211
217
  * Formats error message for number choice parser.
212
218
  */
213
- function formatNumberChoiceError(input, validChoices, allChoices, options) {
214
- if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, validChoices) : options.errors.invalidChoice;
219
+ function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice) {
220
+ if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, validChoices) : invalidChoice;
215
221
  return formatDefaultChoiceError(input, allChoices);
216
222
  }
217
223
  /**
@@ -242,19 +248,22 @@ function string(options = {}) {
242
248
  if (options.pattern != null && !(options.pattern instanceof RegExp)) throw new TypeError(`Expected pattern to be a RegExp, but got: ${Object.prototype.toString.call(options.pattern)}`);
243
249
  const metavar = options.metavar ?? "STRING";
244
250
  require_nonempty.ensureNonEmptyString(metavar);
251
+ const patternSource = options.pattern?.source ?? null;
252
+ const patternFlags = options.pattern?.flags ?? null;
253
+ const patternMismatch = options.errors?.patternMismatch;
245
254
  return {
246
255
  $mode: "sync",
247
256
  metavar,
248
257
  parse(input) {
249
- if (options.pattern != null) {
250
- const pattern = new RegExp(options.pattern.source, options.pattern.flags);
258
+ if (patternSource != null && patternFlags != null) {
259
+ const pattern = new RegExp(patternSource, patternFlags);
251
260
  if (pattern.test(input)) return {
252
261
  success: true,
253
262
  value: input
254
263
  };
255
264
  return {
256
265
  success: false,
257
- error: options.errors?.patternMismatch ? typeof options.errors.patternMismatch === "function" ? options.errors.patternMismatch(input, options.pattern) : options.errors.patternMismatch : require_message.message`Expected a string matching pattern ${require_message.text(options.pattern.source)}, but got ${input}.`
266
+ error: patternMismatch ? typeof patternMismatch === "function" ? patternMismatch(input, pattern) : patternMismatch : require_message.message`Expected a string matching pattern ${require_message.text(patternSource)}, but got ${input}.`
258
267
  };
259
268
  }
260
269
  return {
@@ -440,21 +449,24 @@ function float(options = {}) {
440
449
  * @returns A {@link ValueParser} that converts string input to `URL` objects.
441
450
  */
442
451
  function url(options = {}) {
443
- const allowedProtocols = options.allowedProtocols?.map((p) => p.toLowerCase());
452
+ const originalProtocols = options.allowedProtocols != null ? Object.freeze([...options.allowedProtocols]) : void 0;
453
+ const allowedProtocols = options.allowedProtocols != null ? Object.freeze(options.allowedProtocols.map((p) => p.toLowerCase())) : void 0;
444
454
  const metavar = options.metavar ?? "URL";
445
455
  require_nonempty.ensureNonEmptyString(metavar);
456
+ const invalidUrl = options.errors?.invalidUrl;
457
+ const disallowedProtocol = options.errors?.disallowedProtocol;
446
458
  return {
447
459
  $mode: "sync",
448
460
  metavar,
449
461
  parse(input) {
450
462
  if (!URL.canParse(input)) return {
451
463
  success: false,
452
- error: options.errors?.invalidUrl ? typeof options.errors.invalidUrl === "function" ? options.errors.invalidUrl(input) : options.errors.invalidUrl : require_message.message`Invalid URL: ${input}.`
464
+ error: invalidUrl ? typeof invalidUrl === "function" ? invalidUrl(input) : invalidUrl : require_message.message`Invalid URL: ${input}.`
453
465
  };
454
466
  const url$1 = new URL(input);
455
467
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
456
468
  success: false,
457
- error: options.errors?.disallowedProtocol ? typeof options.errors.disallowedProtocol === "function" ? options.errors.disallowedProtocol(url$1.protocol, options.allowedProtocols) : options.errors.disallowedProtocol : require_message.message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
469
+ error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : require_message.message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
458
470
  };
459
471
  return {
460
472
  success: true,
@@ -508,7 +520,7 @@ function locale(options = {}) {
508
520
  };
509
521
  },
510
522
  format(value) {
511
- return value.baseName;
523
+ return value.toString();
512
524
  },
513
525
  *suggest(prefix) {
514
526
  const commonLocales = [
@@ -758,24 +770,27 @@ function uuid(options = {}) {
758
770
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
759
771
  const metavar = options.metavar ?? "UUID";
760
772
  require_nonempty.ensureNonEmptyString(metavar);
773
+ const allowedVersions = options.allowedVersions != null ? Object.freeze([...options.allowedVersions]) : null;
774
+ const invalidUuid = options.errors?.invalidUuid;
775
+ const disallowedVersion = options.errors?.disallowedVersion;
761
776
  return {
762
777
  $mode: "sync",
763
778
  metavar,
764
779
  parse(input) {
765
780
  if (!uuidRegex.test(input)) return {
766
781
  success: false,
767
- error: options.errors?.invalidUuid ? typeof options.errors.invalidUuid === "function" ? options.errors.invalidUuid(input) : options.errors.invalidUuid : require_message.message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
782
+ error: invalidUuid ? typeof invalidUuid === "function" ? invalidUuid(input) : invalidUuid : require_message.message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
768
783
  };
769
- if (options.allowedVersions != null && options.allowedVersions.length > 0) {
784
+ if (allowedVersions != null && allowedVersions.length > 0) {
770
785
  const versionChar = input.charAt(14);
771
786
  const version = parseInt(versionChar, 16);
772
- if (!options.allowedVersions.includes(version)) return {
787
+ if (!allowedVersions.includes(version)) return {
773
788
  success: false,
774
- error: options.errors?.disallowedVersion ? typeof options.errors.disallowedVersion === "function" ? options.errors.disallowedVersion(version, options.allowedVersions) : options.errors.disallowedVersion : (() => {
789
+ error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, allowedVersions) : disallowedVersion : (() => {
775
790
  let expectedVersions = require_message.message``;
776
791
  let i = 0;
777
- for (const v of options.allowedVersions) {
778
- expectedVersions = i < 1 ? require_message.message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= options.allowedVersions.length ? require_message.message`${expectedVersions}, or ${v.toLocaleString("en")}` : require_message.message`${expectedVersions}, ${v.toLocaleString("en")}`;
792
+ for (const v of allowedVersions) {
793
+ expectedVersions = i < 1 ? require_message.message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= allowedVersions.length ? require_message.message`${expectedVersions}, or ${v.toLocaleString("en")}` : require_message.message`${expectedVersions}, ${v.toLocaleString("en")}`;
779
794
  i++;
780
795
  }
781
796
  return require_message.message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`;
@@ -1220,7 +1235,9 @@ function email(options) {
1220
1235
  const allowMultiple = options?.allowMultiple ?? false;
1221
1236
  const allowDisplayName = options?.allowDisplayName ?? false;
1222
1237
  const lowercase = options?.lowercase ?? false;
1223
- const allowedDomains = options?.allowedDomains;
1238
+ const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
1239
+ const invalidEmail = options?.errors?.invalidEmail;
1240
+ const domainNotAllowed = options?.errors?.domainNotAllowed;
1224
1241
  const atextRegex = /^[a-zA-Z0-9._+-]+$/;
1225
1242
  function validateEmail(input) {
1226
1243
  const trimmed = input.trim();
@@ -1271,7 +1288,7 @@ function email(options) {
1271
1288
  for (const email$1 of emails) {
1272
1289
  const validated = validateEmail(email$1);
1273
1290
  if (validated === null) {
1274
- const errorMsg = options?.errors?.invalidEmail;
1291
+ const errorMsg = invalidEmail;
1275
1292
  const msg = typeof errorMsg === "function" ? errorMsg(email$1) : errorMsg ?? require_message.message`Expected a valid email address, but got ${email$1}.`;
1276
1293
  return {
1277
1294
  success: false,
@@ -1283,7 +1300,7 @@ function email(options) {
1283
1300
  const domain$1 = validated.substring(atIndex + 1).toLowerCase();
1284
1301
  const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
1285
1302
  if (!isAllowed) {
1286
- const errorMsg = options?.errors?.domainNotAllowed;
1303
+ const errorMsg = domainNotAllowed;
1287
1304
  if (typeof errorMsg === "function") return {
1288
1305
  success: false,
1289
1306
  error: errorMsg(validated, allowedDomains)
@@ -1317,7 +1334,7 @@ function email(options) {
1317
1334
  } else {
1318
1335
  const validated = validateEmail(input);
1319
1336
  if (validated === null) {
1320
- const errorMsg = options?.errors?.invalidEmail;
1337
+ const errorMsg = invalidEmail;
1321
1338
  const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid email address, but got ${input}.`;
1322
1339
  return {
1323
1340
  success: false,
@@ -1329,7 +1346,7 @@ function email(options) {
1329
1346
  const domain$1 = validated.substring(atIndex + 1).toLowerCase();
1330
1347
  const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
1331
1348
  if (!isAllowed) {
1332
- const errorMsg = options?.errors?.domainNotAllowed;
1349
+ const errorMsg = domainNotAllowed;
1333
1350
  if (typeof errorMsg === "function") return {
1334
1351
  success: false,
1335
1352
  error: errorMsg(validated, allowedDomains)
@@ -1774,17 +1791,20 @@ function macAddress(options) {
1774
1791
  function domain(options) {
1775
1792
  const metavar = options?.metavar ?? "DOMAIN";
1776
1793
  const allowSubdomains = options?.allowSubdomains ?? true;
1777
- const allowedTLDs = options?.allowedTLDs;
1794
+ const allowedTLDs = options?.allowedTLDs != null ? Object.freeze([...options.allowedTLDs]) : void 0;
1778
1795
  const minLabels = options?.minLabels ?? 2;
1779
1796
  const lowercase = options?.lowercase ?? false;
1780
- const errors = options?.errors;
1797
+ const invalidDomain = options?.errors?.invalidDomain;
1798
+ const tooFewLabels = options?.errors?.tooFewLabels;
1799
+ const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
1800
+ const tldNotAllowed = options?.errors?.tldNotAllowed;
1781
1801
  const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
1782
1802
  return {
1783
1803
  $mode: "sync",
1784
1804
  metavar,
1785
1805
  parse(input) {
1786
1806
  if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
1787
- const errorMsg = errors?.invalidDomain;
1807
+ const errorMsg = invalidDomain;
1788
1808
  if (typeof errorMsg === "function") return {
1789
1809
  success: false,
1790
1810
  error: errorMsg(input)
@@ -1809,7 +1829,7 @@ function domain(options) {
1809
1829
  };
1810
1830
  }
1811
1831
  if (input.includes("..")) {
1812
- const errorMsg = errors?.invalidDomain;
1832
+ const errorMsg = invalidDomain;
1813
1833
  if (typeof errorMsg === "function") return {
1814
1834
  success: false,
1815
1835
  error: errorMsg(input)
@@ -1835,7 +1855,7 @@ function domain(options) {
1835
1855
  }
1836
1856
  const labels = input.split(".");
1837
1857
  for (const label of labels) if (!labelRegex.test(label)) {
1838
- const errorMsg = errors?.invalidDomain;
1858
+ const errorMsg = invalidDomain;
1839
1859
  if (typeof errorMsg === "function") return {
1840
1860
  success: false,
1841
1861
  error: errorMsg(input)
@@ -1860,7 +1880,7 @@ function domain(options) {
1860
1880
  };
1861
1881
  }
1862
1882
  if (labels.length < minLabels) {
1863
- const errorMsg = errors?.tooFewLabels;
1883
+ const errorMsg = tooFewLabels;
1864
1884
  if (typeof errorMsg === "function") return {
1865
1885
  success: false,
1866
1886
  error: errorMsg(input, minLabels)
@@ -1885,7 +1905,7 @@ function domain(options) {
1885
1905
  };
1886
1906
  }
1887
1907
  if (!allowSubdomains && labels.length > 2) {
1888
- const errorMsg = errors?.subdomainsNotAllowed;
1908
+ const errorMsg = subdomainsNotAllowed;
1889
1909
  if (typeof errorMsg === "function") return {
1890
1910
  success: false,
1891
1911
  error: errorMsg(input)
@@ -1914,7 +1934,7 @@ function domain(options) {
1914
1934
  const tldLower = tld.toLowerCase();
1915
1935
  const allowedTLDsLower = allowedTLDs.map((t) => t.toLowerCase());
1916
1936
  if (!allowedTLDsLower.includes(tldLower)) {
1917
- const errorMsg = errors?.tldNotAllowed;
1937
+ const errorMsg = tldNotAllowed;
1918
1938
  if (typeof errorMsg === "function") return {
1919
1939
  success: false,
1920
1940
  error: errorMsg(tld, allowedTLDs)
@@ -207,6 +207,10 @@ declare function isValueParser<M extends Mode, T>(object: unknown): object is Va
207
207
  * @returns A {@link ValueParser} that checks if the input matches one of the
208
208
  * specified values.
209
209
  * @throws {TypeError} If the choices array is empty.
210
+ * @throws {TypeError} If any choice is an empty string.
211
+ * @throws {TypeError} If any choice is not a string.
212
+ * @throws {TypeError} If choices contain a mix of strings and numbers.
213
+ * @throws {TypeError} If `caseInsensitive` is not a boolean.
210
214
  * @throws {TypeError} If `caseInsensitive` is `true` and multiple choices
211
215
  * normalize to the same lowercase value.
212
216
  */
@@ -223,6 +227,9 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
223
227
  * @returns A {@link ValueParser} that checks if the input matches one of the
224
228
  * specified values.
225
229
  * @throws {TypeError} If the choices array is empty.
230
+ * @throws {TypeError} If any choice is not a number.
231
+ * @throws {TypeError} If any choice is `NaN`.
232
+ * @throws {TypeError} If choices contain a mix of strings and numbers.
226
233
  * @since 0.9.0
227
234
  */
228
235
  declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
@@ -207,6 +207,10 @@ declare function isValueParser<M extends Mode, T>(object: unknown): object is Va
207
207
  * @returns A {@link ValueParser} that checks if the input matches one of the
208
208
  * specified values.
209
209
  * @throws {TypeError} If the choices array is empty.
210
+ * @throws {TypeError} If any choice is an empty string.
211
+ * @throws {TypeError} If any choice is not a string.
212
+ * @throws {TypeError} If choices contain a mix of strings and numbers.
213
+ * @throws {TypeError} If `caseInsensitive` is not a boolean.
210
214
  * @throws {TypeError} If `caseInsensitive` is `true` and multiple choices
211
215
  * normalize to the same lowercase value.
212
216
  */
@@ -223,6 +227,9 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
223
227
  * @returns A {@link ValueParser} that checks if the input matches one of the
224
228
  * specified values.
225
229
  * @throws {TypeError} If the choices array is empty.
230
+ * @throws {TypeError} If any choice is not a number.
231
+ * @throws {TypeError} If any choice is `NaN`.
232
+ * @throws {TypeError} If choices contain a mix of strings and numbers.
226
233
  * @since 0.9.0
227
234
  */
228
235
  declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
@@ -15,10 +15,14 @@ function isValueParser(object) {
15
15
  */
16
16
  function choice(choices, options = {}) {
17
17
  if (choices.length < 1) throw new TypeError("Expected at least one choice, but got an empty array.");
18
+ if (choices.some((c) => c === "")) throw new TypeError("Empty strings are not allowed as choices.");
19
+ for (const c of choices) if (typeof c !== "string" && typeof c !== "number") throw new TypeError(`Expected every choice to be a string or number, but got ${typeof c}.`);
20
+ const isNumber = typeof choices[0] === "number";
21
+ for (const c of choices) if (isNumber ? typeof c !== "number" : typeof c !== "string") throw new TypeError(`Expected every choice to be the same type, but got both ${isNumber ? "number" : "string"} and ${typeof c}.`);
22
+ if (isNumber && choices.some((v) => Number.isNaN(v))) throw new TypeError("NaN is not allowed in number choices.");
18
23
  const metavar = options.metavar ?? "TYPE";
19
24
  ensureNonEmptyString(metavar);
20
- const isNumberChoice = choices.length > 0 && typeof choices[0] === "number";
21
- if (isNumberChoice) {
25
+ if (isNumber) {
22
26
  const numberChoices = (() => {
23
27
  const seen = /* @__PURE__ */ new Set();
24
28
  let hasPositiveZero = false;
@@ -39,17 +43,16 @@ function choice(choices, options = {}) {
39
43
  }
40
44
  return result;
41
45
  })();
42
- const numberOptions = options;
43
- const hasNaN = numberChoices.some((v) => Number.isNaN(v));
44
- const validNumberChoices = hasNaN ? numberChoices.filter((v) => !Number.isNaN(v)) : numberChoices;
46
+ const numberInvalidChoice = options.errors?.invalidChoice;
45
47
  const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
48
+ const frozenNumberChoices = Object.freeze(numberChoices);
46
49
  return {
47
50
  $mode: "sync",
48
51
  metavar,
49
- choices: hasNaN ? validNumberChoices : numberChoices,
52
+ choices: frozenNumberChoices,
50
53
  parse(input) {
51
54
  const index = numberStrings.indexOf(input);
52
- if (index >= 0 && !Number.isNaN(numberChoices[index])) return {
55
+ if (index >= 0) return {
53
56
  success: true,
54
57
  value: numberChoices[index]
55
58
  };
@@ -94,7 +97,7 @@ function choice(choices, options = {}) {
94
97
  }
95
98
  return {
96
99
  success: false,
97
- error: formatNumberChoiceError(input, validNumberChoices, numberChoices, numberOptions)
100
+ error: formatNumberChoiceError(input, numberChoices, numberChoices, numberInvalidChoice)
98
101
  };
99
102
  },
100
103
  format(value) {
@@ -108,10 +111,12 @@ function choice(choices, options = {}) {
108
111
  }
109
112
  };
110
113
  }
111
- const stringChoices = [...new Set(choices)];
114
+ const stringChoices = Object.freeze([...new Set(choices)]);
112
115
  const stringOptions = options;
113
- const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
114
- if (stringOptions.caseInsensitive) {
116
+ if (stringOptions.caseInsensitive !== void 0 && typeof stringOptions.caseInsensitive !== "boolean") throw new TypeError(`Expected caseInsensitive to be a boolean, but got ${typeof stringOptions.caseInsensitive}: ${String(stringOptions.caseInsensitive)}.`);
117
+ const caseInsensitive = stringOptions.caseInsensitive ?? false;
118
+ const normalizedValues = caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
119
+ if (caseInsensitive) {
115
120
  const seen = /* @__PURE__ */ new Map();
116
121
  for (let i = 0; i < stringChoices.length; i++) {
117
122
  const nv = normalizedValues[i];
@@ -121,16 +126,17 @@ function choice(choices, options = {}) {
121
126
  seen.set(nv, original);
122
127
  }
123
128
  }
129
+ const stringInvalidChoice = stringOptions.errors?.invalidChoice;
124
130
  return {
125
131
  $mode: "sync",
126
132
  metavar,
127
133
  choices: stringChoices,
128
134
  parse(input) {
129
- const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
135
+ const normalizedInput = caseInsensitive ? input.toLowerCase() : input;
130
136
  const index = normalizedValues.indexOf(normalizedInput);
131
137
  if (index < 0) return {
132
138
  success: false,
133
- error: formatStringChoiceError(input, stringChoices, stringOptions)
139
+ error: formatStringChoiceError(input, stringChoices, stringInvalidChoice)
134
140
  };
135
141
  return {
136
142
  success: true,
@@ -141,9 +147,9 @@ function choice(choices, options = {}) {
141
147
  return String(value);
142
148
  },
143
149
  suggest(prefix) {
144
- const normalizedPrefix = stringOptions.caseInsensitive ? prefix.toLowerCase() : prefix;
150
+ const normalizedPrefix = caseInsensitive ? prefix.toLowerCase() : prefix;
145
151
  return stringChoices.filter((value) => {
146
- const normalizedValue = stringOptions.caseInsensitive ? value.toLowerCase() : value;
152
+ const normalizedValue = caseInsensitive ? value.toLowerCase() : value;
147
153
  return normalizedValue.startsWith(normalizedPrefix);
148
154
  }).map((value) => ({
149
155
  kind: "literal",
@@ -203,15 +209,15 @@ function normalizeDecimal(s) {
203
209
  /**
204
210
  * Formats error message for string choice parser.
205
211
  */
206
- function formatStringChoiceError(input, choices, options) {
207
- if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
212
+ function formatStringChoiceError(input, choices, invalidChoice) {
213
+ if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, choices) : invalidChoice;
208
214
  return formatDefaultChoiceError(input, choices);
209
215
  }
210
216
  /**
211
217
  * Formats error message for number choice parser.
212
218
  */
213
- function formatNumberChoiceError(input, validChoices, allChoices, options) {
214
- if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, validChoices) : options.errors.invalidChoice;
219
+ function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice) {
220
+ if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, validChoices) : invalidChoice;
215
221
  return formatDefaultChoiceError(input, allChoices);
216
222
  }
217
223
  /**
@@ -242,19 +248,22 @@ function string(options = {}) {
242
248
  if (options.pattern != null && !(options.pattern instanceof RegExp)) throw new TypeError(`Expected pattern to be a RegExp, but got: ${Object.prototype.toString.call(options.pattern)}`);
243
249
  const metavar = options.metavar ?? "STRING";
244
250
  ensureNonEmptyString(metavar);
251
+ const patternSource = options.pattern?.source ?? null;
252
+ const patternFlags = options.pattern?.flags ?? null;
253
+ const patternMismatch = options.errors?.patternMismatch;
245
254
  return {
246
255
  $mode: "sync",
247
256
  metavar,
248
257
  parse(input) {
249
- if (options.pattern != null) {
250
- const pattern = new RegExp(options.pattern.source, options.pattern.flags);
258
+ if (patternSource != null && patternFlags != null) {
259
+ const pattern = new RegExp(patternSource, patternFlags);
251
260
  if (pattern.test(input)) return {
252
261
  success: true,
253
262
  value: input
254
263
  };
255
264
  return {
256
265
  success: false,
257
- error: options.errors?.patternMismatch ? typeof options.errors.patternMismatch === "function" ? options.errors.patternMismatch(input, options.pattern) : options.errors.patternMismatch : message`Expected a string matching pattern ${text(options.pattern.source)}, but got ${input}.`
266
+ error: patternMismatch ? typeof patternMismatch === "function" ? patternMismatch(input, pattern) : patternMismatch : message`Expected a string matching pattern ${text(patternSource)}, but got ${input}.`
258
267
  };
259
268
  }
260
269
  return {
@@ -440,21 +449,24 @@ function float(options = {}) {
440
449
  * @returns A {@link ValueParser} that converts string input to `URL` objects.
441
450
  */
442
451
  function url(options = {}) {
443
- const allowedProtocols = options.allowedProtocols?.map((p) => p.toLowerCase());
452
+ const originalProtocols = options.allowedProtocols != null ? Object.freeze([...options.allowedProtocols]) : void 0;
453
+ const allowedProtocols = options.allowedProtocols != null ? Object.freeze(options.allowedProtocols.map((p) => p.toLowerCase())) : void 0;
444
454
  const metavar = options.metavar ?? "URL";
445
455
  ensureNonEmptyString(metavar);
456
+ const invalidUrl = options.errors?.invalidUrl;
457
+ const disallowedProtocol = options.errors?.disallowedProtocol;
446
458
  return {
447
459
  $mode: "sync",
448
460
  metavar,
449
461
  parse(input) {
450
462
  if (!URL.canParse(input)) return {
451
463
  success: false,
452
- error: options.errors?.invalidUrl ? typeof options.errors.invalidUrl === "function" ? options.errors.invalidUrl(input) : options.errors.invalidUrl : message`Invalid URL: ${input}.`
464
+ error: invalidUrl ? typeof invalidUrl === "function" ? invalidUrl(input) : invalidUrl : message`Invalid URL: ${input}.`
453
465
  };
454
466
  const url$1 = new URL(input);
455
467
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
456
468
  success: false,
457
- error: options.errors?.disallowedProtocol ? typeof options.errors.disallowedProtocol === "function" ? options.errors.disallowedProtocol(url$1.protocol, options.allowedProtocols) : options.errors.disallowedProtocol : message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
469
+ error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
458
470
  };
459
471
  return {
460
472
  success: true,
@@ -508,7 +520,7 @@ function locale(options = {}) {
508
520
  };
509
521
  },
510
522
  format(value) {
511
- return value.baseName;
523
+ return value.toString();
512
524
  },
513
525
  *suggest(prefix) {
514
526
  const commonLocales = [
@@ -758,24 +770,27 @@ function uuid(options = {}) {
758
770
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
759
771
  const metavar = options.metavar ?? "UUID";
760
772
  ensureNonEmptyString(metavar);
773
+ const allowedVersions = options.allowedVersions != null ? Object.freeze([...options.allowedVersions]) : null;
774
+ const invalidUuid = options.errors?.invalidUuid;
775
+ const disallowedVersion = options.errors?.disallowedVersion;
761
776
  return {
762
777
  $mode: "sync",
763
778
  metavar,
764
779
  parse(input) {
765
780
  if (!uuidRegex.test(input)) return {
766
781
  success: false,
767
- error: options.errors?.invalidUuid ? typeof options.errors.invalidUuid === "function" ? options.errors.invalidUuid(input) : options.errors.invalidUuid : message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
782
+ error: invalidUuid ? typeof invalidUuid === "function" ? invalidUuid(input) : invalidUuid : message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
768
783
  };
769
- if (options.allowedVersions != null && options.allowedVersions.length > 0) {
784
+ if (allowedVersions != null && allowedVersions.length > 0) {
770
785
  const versionChar = input.charAt(14);
771
786
  const version = parseInt(versionChar, 16);
772
- if (!options.allowedVersions.includes(version)) return {
787
+ if (!allowedVersions.includes(version)) return {
773
788
  success: false,
774
- error: options.errors?.disallowedVersion ? typeof options.errors.disallowedVersion === "function" ? options.errors.disallowedVersion(version, options.allowedVersions) : options.errors.disallowedVersion : (() => {
789
+ error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, allowedVersions) : disallowedVersion : (() => {
775
790
  let expectedVersions = message``;
776
791
  let i = 0;
777
- for (const v of options.allowedVersions) {
778
- expectedVersions = i < 1 ? message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= options.allowedVersions.length ? message`${expectedVersions}, or ${v.toLocaleString("en")}` : message`${expectedVersions}, ${v.toLocaleString("en")}`;
792
+ for (const v of allowedVersions) {
793
+ expectedVersions = i < 1 ? message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= allowedVersions.length ? message`${expectedVersions}, or ${v.toLocaleString("en")}` : message`${expectedVersions}, ${v.toLocaleString("en")}`;
779
794
  i++;
780
795
  }
781
796
  return message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`;
@@ -1220,7 +1235,9 @@ function email(options) {
1220
1235
  const allowMultiple = options?.allowMultiple ?? false;
1221
1236
  const allowDisplayName = options?.allowDisplayName ?? false;
1222
1237
  const lowercase = options?.lowercase ?? false;
1223
- const allowedDomains = options?.allowedDomains;
1238
+ const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
1239
+ const invalidEmail = options?.errors?.invalidEmail;
1240
+ const domainNotAllowed = options?.errors?.domainNotAllowed;
1224
1241
  const atextRegex = /^[a-zA-Z0-9._+-]+$/;
1225
1242
  function validateEmail(input) {
1226
1243
  const trimmed = input.trim();
@@ -1271,7 +1288,7 @@ function email(options) {
1271
1288
  for (const email$1 of emails) {
1272
1289
  const validated = validateEmail(email$1);
1273
1290
  if (validated === null) {
1274
- const errorMsg = options?.errors?.invalidEmail;
1291
+ const errorMsg = invalidEmail;
1275
1292
  const msg = typeof errorMsg === "function" ? errorMsg(email$1) : errorMsg ?? message`Expected a valid email address, but got ${email$1}.`;
1276
1293
  return {
1277
1294
  success: false,
@@ -1283,7 +1300,7 @@ function email(options) {
1283
1300
  const domain$1 = validated.substring(atIndex + 1).toLowerCase();
1284
1301
  const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
1285
1302
  if (!isAllowed) {
1286
- const errorMsg = options?.errors?.domainNotAllowed;
1303
+ const errorMsg = domainNotAllowed;
1287
1304
  if (typeof errorMsg === "function") return {
1288
1305
  success: false,
1289
1306
  error: errorMsg(validated, allowedDomains)
@@ -1317,7 +1334,7 @@ function email(options) {
1317
1334
  } else {
1318
1335
  const validated = validateEmail(input);
1319
1336
  if (validated === null) {
1320
- const errorMsg = options?.errors?.invalidEmail;
1337
+ const errorMsg = invalidEmail;
1321
1338
  const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Expected a valid email address, but got ${input}.`;
1322
1339
  return {
1323
1340
  success: false,
@@ -1329,7 +1346,7 @@ function email(options) {
1329
1346
  const domain$1 = validated.substring(atIndex + 1).toLowerCase();
1330
1347
  const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
1331
1348
  if (!isAllowed) {
1332
- const errorMsg = options?.errors?.domainNotAllowed;
1349
+ const errorMsg = domainNotAllowed;
1333
1350
  if (typeof errorMsg === "function") return {
1334
1351
  success: false,
1335
1352
  error: errorMsg(validated, allowedDomains)
@@ -1774,17 +1791,20 @@ function macAddress(options) {
1774
1791
  function domain(options) {
1775
1792
  const metavar = options?.metavar ?? "DOMAIN";
1776
1793
  const allowSubdomains = options?.allowSubdomains ?? true;
1777
- const allowedTLDs = options?.allowedTLDs;
1794
+ const allowedTLDs = options?.allowedTLDs != null ? Object.freeze([...options.allowedTLDs]) : void 0;
1778
1795
  const minLabels = options?.minLabels ?? 2;
1779
1796
  const lowercase = options?.lowercase ?? false;
1780
- const errors = options?.errors;
1797
+ const invalidDomain = options?.errors?.invalidDomain;
1798
+ const tooFewLabels = options?.errors?.tooFewLabels;
1799
+ const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
1800
+ const tldNotAllowed = options?.errors?.tldNotAllowed;
1781
1801
  const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
1782
1802
  return {
1783
1803
  $mode: "sync",
1784
1804
  metavar,
1785
1805
  parse(input) {
1786
1806
  if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
1787
- const errorMsg = errors?.invalidDomain;
1807
+ const errorMsg = invalidDomain;
1788
1808
  if (typeof errorMsg === "function") return {
1789
1809
  success: false,
1790
1810
  error: errorMsg(input)
@@ -1809,7 +1829,7 @@ function domain(options) {
1809
1829
  };
1810
1830
  }
1811
1831
  if (input.includes("..")) {
1812
- const errorMsg = errors?.invalidDomain;
1832
+ const errorMsg = invalidDomain;
1813
1833
  if (typeof errorMsg === "function") return {
1814
1834
  success: false,
1815
1835
  error: errorMsg(input)
@@ -1835,7 +1855,7 @@ function domain(options) {
1835
1855
  }
1836
1856
  const labels = input.split(".");
1837
1857
  for (const label of labels) if (!labelRegex.test(label)) {
1838
- const errorMsg = errors?.invalidDomain;
1858
+ const errorMsg = invalidDomain;
1839
1859
  if (typeof errorMsg === "function") return {
1840
1860
  success: false,
1841
1861
  error: errorMsg(input)
@@ -1860,7 +1880,7 @@ function domain(options) {
1860
1880
  };
1861
1881
  }
1862
1882
  if (labels.length < minLabels) {
1863
- const errorMsg = errors?.tooFewLabels;
1883
+ const errorMsg = tooFewLabels;
1864
1884
  if (typeof errorMsg === "function") return {
1865
1885
  success: false,
1866
1886
  error: errorMsg(input, minLabels)
@@ -1885,7 +1905,7 @@ function domain(options) {
1885
1905
  };
1886
1906
  }
1887
1907
  if (!allowSubdomains && labels.length > 2) {
1888
- const errorMsg = errors?.subdomainsNotAllowed;
1908
+ const errorMsg = subdomainsNotAllowed;
1889
1909
  if (typeof errorMsg === "function") return {
1890
1910
  success: false,
1891
1911
  error: errorMsg(input)
@@ -1914,7 +1934,7 @@ function domain(options) {
1914
1934
  const tldLower = tld.toLowerCase();
1915
1935
  const allowedTLDsLower = allowedTLDs.map((t) => t.toLowerCase());
1916
1936
  if (!allowedTLDsLower.includes(tldLower)) {
1917
- const errorMsg = errors?.tldNotAllowed;
1937
+ const errorMsg = tldNotAllowed;
1918
1938
  if (typeof errorMsg === "function") return {
1919
1939
  success: false,
1920
1940
  error: errorMsg(tld, allowedTLDs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.788+1f1c275f",
3
+ "version": "1.0.0-dev.886+4b42a9bd",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",