@optique/core 1.2.0-dev.2298 → 1.2.0-dev.2302

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.
@@ -1,9 +1,34 @@
1
1
  const require_message = require('./message.cjs');
2
+ const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
2
3
  const require_internal_dependency = require('./internal/dependency.cjs');
3
4
  const require_suggestion = require('./suggestion.cjs');
4
5
  const require_nonempty = require('./nonempty.cjs');
5
6
 
6
7
  //#region src/valueparser.ts
8
+ function transformValueParserResult(result, mapping) {
9
+ if (!result.success) return result;
10
+ const preserveSnapshot = (mapped) => {
11
+ const snapshot = require_internal_dependency.getSnapshottedDefaultDependencyValues(result);
12
+ return snapshot == null ? mapped : require_internal_dependency.snapshotDefaultDependencyValues(mapped, snapshot);
13
+ };
14
+ if (result.deferred) try {
15
+ return preserveSnapshot({
16
+ success: true,
17
+ value: mapping.map(result.value),
18
+ deferred: true
19
+ });
20
+ } catch {
21
+ return preserveSnapshot({
22
+ success: true,
23
+ value: void 0,
24
+ deferred: true
25
+ });
26
+ }
27
+ return preserveSnapshot({
28
+ success: true,
29
+ value: mapping.map(result.value)
30
+ });
31
+ }
7
32
  /**
8
33
  * A predicate function that checks if an object is a {@link ValueParser}.
9
34
  * @param object The object to check.
@@ -191,6 +216,187 @@ function choice(choices, options = {}) {
191
216
  }
192
217
  };
193
218
  }
219
+ function biject(mapping) {
220
+ if (Array.isArray(mapping)) throw new TypeError("Expected biject mapping to be a non-array object.");
221
+ const keys = [];
222
+ for (const key in mapping) if (Object.prototype.hasOwnProperty.call(mapping, key)) keys.push(key);
223
+ if (keys.length < 1) throw new RangeError("Expected at least one biject entry.");
224
+ const source = choice(keys);
225
+ const forward = /* @__PURE__ */ new Map();
226
+ const reverse = /* @__PURE__ */ new Map();
227
+ for (const key of keys) {
228
+ const value = mapping[key];
229
+ if (reverse.has(value)) throw new RangeError(`Duplicate biject value for key ${JSON.stringify(key)}.`);
230
+ forward.set(key, value);
231
+ reverse.set(value, key);
232
+ }
233
+ const parser = transform(source, {
234
+ map(value) {
235
+ return forward.get(value);
236
+ },
237
+ unmap(value) {
238
+ const key = reverse.get(value);
239
+ if (key !== void 0) return key;
240
+ try {
241
+ return String(value);
242
+ } catch {
243
+ return "";
244
+ }
245
+ }
246
+ });
247
+ Object.defineProperty(parser, "validate", {
248
+ value(value) {
249
+ if (reverse.has(value)) return {
250
+ success: true,
251
+ value
252
+ };
253
+ let input;
254
+ try {
255
+ input = String(value);
256
+ } catch {
257
+ input = "";
258
+ }
259
+ const result = source.parse(input);
260
+ if (!result.success) return result;
261
+ return {
262
+ success: false,
263
+ error: formatDefaultChoiceError(input, keys)
264
+ };
265
+ },
266
+ configurable: true,
267
+ enumerable: true
268
+ });
269
+ return parser;
270
+ }
271
+ /**
272
+ * Creates a value parser that transforms the result of another value parser.
273
+ *
274
+ * This is useful when an existing value parser already describes the accepted
275
+ * CLI spelling, suggestions, and error messages, but your application wants a
276
+ * different result type. For example, a string `choice()` can be transformed
277
+ * into an internal enum, tagged object, or other domain type.
278
+ *
279
+ * Unlike parser-level `map()`, value parser transformation needs an inverse
280
+ * mapping. The `unmap` function lets the transformed parser format values,
281
+ * validate fallback/default values, and reuse the wrapped parser's metadata
282
+ * without guessing how to serialize the transformed type.
283
+ *
284
+ * Transform functions are synchronous. If the wrapped parser is async, the
285
+ * returned parser is async too, but `map` and `unmap` still run synchronously
286
+ * after the wrapped parser resolves.
287
+ *
288
+ * @template M The execution mode of the wrapped parser.
289
+ * @template T The value type produced by the wrapped parser.
290
+ * @template U The value type produced by the transformed parser.
291
+ * @param parser The value parser to transform.
292
+ * @param mapping Mapping functions between the wrapped and transformed value
293
+ * types.
294
+ * @returns A value parser that accepts the same input as `parser` and produces
295
+ * transformed values.
296
+ * @since 1.2.0
297
+ */
298
+ function transform(parser, mapping) {
299
+ const normalize = parser.normalize?.bind(parser);
300
+ const suggest = parser.suggest?.bind(parser);
301
+ const transformedChoices = parser.choices == null ? void 0 : Object.freeze(parser.choices.map((choice$1) => mapping.map(choice$1)));
302
+ const transformed = {
303
+ mode: parser.mode,
304
+ metavar: parser.metavar,
305
+ placeholder: void 0,
306
+ ...transformedChoices == null ? {} : { choices: transformedChoices },
307
+ parse(input) {
308
+ return require_mode_dispatch.mapModeValue(parser.mode, parser.parse(input), (result) => transformValueParserResult(result, mapping));
309
+ },
310
+ format(value) {
311
+ return parser.format(mapping.unmap(value));
312
+ },
313
+ ...normalize == null ? {} : { normalize(value) {
314
+ return mapping.map(normalize(mapping.unmap(value)));
315
+ } },
316
+ ...suggest == null ? {} : { suggest(prefix) {
317
+ return suggest(prefix);
318
+ } }
319
+ };
320
+ Object.defineProperty(transformed, "placeholder", {
321
+ get() {
322
+ try {
323
+ return mapping.map(parser.placeholder);
324
+ } catch {
325
+ return void 0;
326
+ }
327
+ },
328
+ configurable: true,
329
+ enumerable: false
330
+ });
331
+ if (typeof parser.validate === "function") {
332
+ const validate = parser.validate.bind(parser);
333
+ Object.defineProperty(transformed, "validate", {
334
+ value(value) {
335
+ const result = validate(mapping.unmap(value));
336
+ return result.success ? {
337
+ success: true,
338
+ value: mapping.map(result.value)
339
+ } : result;
340
+ },
341
+ configurable: true,
342
+ enumerable: true
343
+ });
344
+ } else if (parser.mode === "sync") {
345
+ const syncParser = parser;
346
+ Object.defineProperty(transformed, "validate", {
347
+ value(value) {
348
+ const result = syncParser.parse(syncParser.format(mapping.unmap(value)));
349
+ return result.success ? {
350
+ success: true,
351
+ value: mapping.map(result.value)
352
+ } : result;
353
+ },
354
+ configurable: true,
355
+ enumerable: true
356
+ });
357
+ }
358
+ if (require_internal_dependency.isDerivedValueParser(parser)) preserveTransformedDerivedMetadata(transformed, parser, mapping);
359
+ return transformed;
360
+ }
361
+ function preserveTransformedDerivedMetadata(transformed, parser, mapping) {
362
+ Object.defineProperties(transformed, {
363
+ [require_internal_dependency.derivedValueParserMarker]: {
364
+ value: true,
365
+ enumerable: true
366
+ },
367
+ [require_internal_dependency.dependencyId]: {
368
+ value: parser[require_internal_dependency.dependencyId],
369
+ enumerable: true
370
+ },
371
+ [require_internal_dependency.parseWithDependency]: {
372
+ value(input, dependencyValue) {
373
+ return require_mode_dispatch.mapMaybePromiseByMode(parser.mode, parser[require_internal_dependency.parseWithDependency](input, dependencyValue), (result) => transformValueParserResult(result, mapping));
374
+ },
375
+ enumerable: true
376
+ }
377
+ });
378
+ if (require_internal_dependency.dependencyIds in parser && parser[require_internal_dependency.dependencyIds] != null) Object.defineProperty(transformed, require_internal_dependency.dependencyIds, {
379
+ value: parser[require_internal_dependency.dependencyIds],
380
+ enumerable: true
381
+ });
382
+ if (require_internal_dependency.defaultValues in parser && parser[require_internal_dependency.defaultValues] != null) Object.defineProperty(transformed, require_internal_dependency.defaultValues, {
383
+ value: parser[require_internal_dependency.defaultValues],
384
+ enumerable: true
385
+ });
386
+ if (require_internal_dependency.singleDefaultValue in parser && parser[require_internal_dependency.singleDefaultValue] != null) Object.defineProperty(transformed, require_internal_dependency.singleDefaultValue, {
387
+ value: parser[require_internal_dependency.singleDefaultValue],
388
+ enumerable: true
389
+ });
390
+ if (require_internal_dependency.suggestWithDependency in parser && parser[require_internal_dependency.suggestWithDependency] != null) {
391
+ const suggest = parser[require_internal_dependency.suggestWithDependency];
392
+ Object.defineProperty(transformed, require_internal_dependency.suggestWithDependency, {
393
+ value(prefix, dependencyValue) {
394
+ return require_mode_dispatch.wrapIterableForMode(parser.mode, suggest(prefix, dependencyValue));
395
+ },
396
+ enumerable: true
397
+ });
398
+ }
399
+ }
194
400
  /**
195
401
  * Validates that an option value, if present, is a boolean.
196
402
  * Throws a {@link TypeError} if the value is defined but not a boolean.
@@ -6336,6 +6542,7 @@ function plainObjectsEqual(a, b) {
6336
6542
  }
6337
6543
 
6338
6544
  //#endregion
6545
+ exports.biject = biject;
6339
6546
  exports.checkBooleanOption = checkBooleanOption;
6340
6547
  exports.checkEnumOption = checkEnumOption;
6341
6548
  exports.choice = choice;
@@ -6364,5 +6571,6 @@ exports.portRange = portRange;
6364
6571
  exports.semVer = semVer;
6365
6572
  exports.socketAddress = socketAddress;
6366
6573
  exports.string = string;
6574
+ exports.transform = transform;
6367
6575
  exports.url = url;
6368
6576
  exports.uuid = uuid;
@@ -327,6 +327,36 @@ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
327
327
  * {@link ChoiceOptionsNumber} for number choices.
328
328
  */
329
329
  type ChoiceOptions = ChoiceOptionsString;
330
+ /**
331
+ * Mapping functions for the {@link transform} value parser combinator.
332
+ *
333
+ * `map` converts values produced by the wrapped parser into the public result
334
+ * type. `unmap` converts public values back to the wrapped parser's type so
335
+ * `format()`, `validate()`, and default-value handling can keep using the
336
+ * wrapped parser's own validation and formatting rules.
337
+ *
338
+ * The two functions should be inverses for the values your CLI accepts:
339
+ * `unmap(map(input))` should return a value accepted by the wrapped parser,
340
+ * and `map(unmap(output))` should preserve valid public values.
341
+ *
342
+ * @template T The value type produced by the wrapped parser.
343
+ * @template U The value type produced by the transformed parser.
344
+ * @since 1.2.0
345
+ */
346
+ interface TransformMapping<T, U> {
347
+ /**
348
+ * Converts a value produced by the wrapped parser into the transformed
349
+ * parser's public value type.
350
+ */
351
+ map(value: T): U;
352
+ /**
353
+ * Converts a public transformed value back into the wrapped parser's value
354
+ * type for formatting and fallback validation.
355
+ */
356
+ unmap(value: U): T;
357
+ }
358
+ type BijectKey<T> = Extract<keyof T, string | number>;
359
+ type BijectValue<T> = T[BijectKey<T>];
330
360
  /**
331
361
  * A predicate function that checks if an object is a {@link ValueParser}.
332
362
  * @param object The object to check.
@@ -374,6 +404,58 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
374
404
  * @since 0.9.0
375
405
  */
376
406
  declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
407
+ /**
408
+ * Creates a value parser from a one-to-one mapping of CLI spellings to values.
409
+ *
410
+ * The mapping's string keys are accepted as command-line input, and each key
411
+ * is parsed into its corresponding value. Values must also be unique using
412
+ * the same equality semantics as `Map` keys, so the parser can format a value
413
+ * back to the original key.
414
+ *
415
+ * This is a convenience wrapper around `choice(Object.keys(mapping))` and
416
+ * {@link transform}. It keeps the input-side metadata from `choice()` while
417
+ * exposing the mapped values as the parser result type.
418
+ *
419
+ * @template T The one-to-one mapping from input strings to parsed values.
420
+ * @param mapping A mapping whose own enumerable string keys are valid inputs.
421
+ * @returns A value parser that accepts one of the mapping keys and returns the
422
+ * corresponding value.
423
+ * @throws {TypeError} If `mapping` is an array, or if any key is the empty
424
+ * string.
425
+ * @throws {RangeError} If the mapping has no own enumerable string keys, or if
426
+ * two keys map to the same value according to `Map` key equality.
427
+ * @since 1.2.0
428
+ */
429
+ declare function biject<const T extends readonly unknown[]>(mapping: T): never;
430
+ declare function biject<const T extends object>(mapping: T): ValueParser<"sync", BijectValue<T>>;
431
+ /**
432
+ * Creates a value parser that transforms the result of another value parser.
433
+ *
434
+ * This is useful when an existing value parser already describes the accepted
435
+ * CLI spelling, suggestions, and error messages, but your application wants a
436
+ * different result type. For example, a string `choice()` can be transformed
437
+ * into an internal enum, tagged object, or other domain type.
438
+ *
439
+ * Unlike parser-level `map()`, value parser transformation needs an inverse
440
+ * mapping. The `unmap` function lets the transformed parser format values,
441
+ * validate fallback/default values, and reuse the wrapped parser's metadata
442
+ * without guessing how to serialize the transformed type.
443
+ *
444
+ * Transform functions are synchronous. If the wrapped parser is async, the
445
+ * returned parser is async too, but `map` and `unmap` still run synchronously
446
+ * after the wrapped parser resolves.
447
+ *
448
+ * @template M The execution mode of the wrapped parser.
449
+ * @template T The value type produced by the wrapped parser.
450
+ * @template U The value type produced by the transformed parser.
451
+ * @param parser The value parser to transform.
452
+ * @param mapping Mapping functions between the wrapped and transformed value
453
+ * types.
454
+ * @returns A value parser that accepts the same input as `parser` and produces
455
+ * transformed values.
456
+ * @since 1.2.0
457
+ */
458
+ declare function transform<M extends Mode, T, U>(parser: ValueParser<M, T>, mapping: TransformMapping<T, U>): ValueParser<M, U>;
377
459
  /**
378
460
  * Validates that an option value, if present, is a boolean.
379
461
  * Throws a {@link TypeError} if the value is defined but not a boolean.
@@ -3197,4 +3279,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
3197
3279
  */
3198
3280
  declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
3199
3281
  //#endregion
3200
- export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
3282
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
@@ -327,6 +327,36 @@ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
327
327
  * {@link ChoiceOptionsNumber} for number choices.
328
328
  */
329
329
  type ChoiceOptions = ChoiceOptionsString;
330
+ /**
331
+ * Mapping functions for the {@link transform} value parser combinator.
332
+ *
333
+ * `map` converts values produced by the wrapped parser into the public result
334
+ * type. `unmap` converts public values back to the wrapped parser's type so
335
+ * `format()`, `validate()`, and default-value handling can keep using the
336
+ * wrapped parser's own validation and formatting rules.
337
+ *
338
+ * The two functions should be inverses for the values your CLI accepts:
339
+ * `unmap(map(input))` should return a value accepted by the wrapped parser,
340
+ * and `map(unmap(output))` should preserve valid public values.
341
+ *
342
+ * @template T The value type produced by the wrapped parser.
343
+ * @template U The value type produced by the transformed parser.
344
+ * @since 1.2.0
345
+ */
346
+ interface TransformMapping<T, U> {
347
+ /**
348
+ * Converts a value produced by the wrapped parser into the transformed
349
+ * parser's public value type.
350
+ */
351
+ map(value: T): U;
352
+ /**
353
+ * Converts a public transformed value back into the wrapped parser's value
354
+ * type for formatting and fallback validation.
355
+ */
356
+ unmap(value: U): T;
357
+ }
358
+ type BijectKey<T> = Extract<keyof T, string | number>;
359
+ type BijectValue<T> = T[BijectKey<T>];
330
360
  /**
331
361
  * A predicate function that checks if an object is a {@link ValueParser}.
332
362
  * @param object The object to check.
@@ -374,6 +404,58 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
374
404
  * @since 0.9.0
375
405
  */
376
406
  declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
407
+ /**
408
+ * Creates a value parser from a one-to-one mapping of CLI spellings to values.
409
+ *
410
+ * The mapping's string keys are accepted as command-line input, and each key
411
+ * is parsed into its corresponding value. Values must also be unique using
412
+ * the same equality semantics as `Map` keys, so the parser can format a value
413
+ * back to the original key.
414
+ *
415
+ * This is a convenience wrapper around `choice(Object.keys(mapping))` and
416
+ * {@link transform}. It keeps the input-side metadata from `choice()` while
417
+ * exposing the mapped values as the parser result type.
418
+ *
419
+ * @template T The one-to-one mapping from input strings to parsed values.
420
+ * @param mapping A mapping whose own enumerable string keys are valid inputs.
421
+ * @returns A value parser that accepts one of the mapping keys and returns the
422
+ * corresponding value.
423
+ * @throws {TypeError} If `mapping` is an array, or if any key is the empty
424
+ * string.
425
+ * @throws {RangeError} If the mapping has no own enumerable string keys, or if
426
+ * two keys map to the same value according to `Map` key equality.
427
+ * @since 1.2.0
428
+ */
429
+ declare function biject<const T extends readonly unknown[]>(mapping: T): never;
430
+ declare function biject<const T extends object>(mapping: T): ValueParser<"sync", BijectValue<T>>;
431
+ /**
432
+ * Creates a value parser that transforms the result of another value parser.
433
+ *
434
+ * This is useful when an existing value parser already describes the accepted
435
+ * CLI spelling, suggestions, and error messages, but your application wants a
436
+ * different result type. For example, a string `choice()` can be transformed
437
+ * into an internal enum, tagged object, or other domain type.
438
+ *
439
+ * Unlike parser-level `map()`, value parser transformation needs an inverse
440
+ * mapping. The `unmap` function lets the transformed parser format values,
441
+ * validate fallback/default values, and reuse the wrapped parser's metadata
442
+ * without guessing how to serialize the transformed type.
443
+ *
444
+ * Transform functions are synchronous. If the wrapped parser is async, the
445
+ * returned parser is async too, but `map` and `unmap` still run synchronously
446
+ * after the wrapped parser resolves.
447
+ *
448
+ * @template M The execution mode of the wrapped parser.
449
+ * @template T The value type produced by the wrapped parser.
450
+ * @template U The value type produced by the transformed parser.
451
+ * @param parser The value parser to transform.
452
+ * @param mapping Mapping functions between the wrapped and transformed value
453
+ * types.
454
+ * @returns A value parser that accepts the same input as `parser` and produces
455
+ * transformed values.
456
+ * @since 1.2.0
457
+ */
458
+ declare function transform<M extends Mode, T, U>(parser: ValueParser<M, T>, mapping: TransformMapping<T, U>): ValueParser<M, U>;
377
459
  /**
378
460
  * Validates that an option value, if present, is a boolean.
379
461
  * Throws a {@link TypeError} if the value is defined but not a boolean.
@@ -3197,4 +3279,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
3197
3279
  */
3198
3280
  declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
3199
3281
  //#endregion
3200
- export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
3282
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
@@ -1,9 +1,34 @@
1
1
  import { cloneMessage, lineBreak, message, metavar, text, valueSet } from "./message.js";
2
- import { isDerivedValueParser } from "./internal/dependency.js";
2
+ import { mapMaybePromiseByMode, mapModeValue, wrapIterableForMode } from "./internal/mode-dispatch.js";
3
+ import { defaultValues, dependencyId, dependencyIds, derivedValueParserMarker, getSnapshottedDefaultDependencyValues, isDerivedValueParser, parseWithDependency, singleDefaultValue, snapshotDefaultDependencyValues, suggestWithDependency } from "./internal/dependency.js";
3
4
  import { appendValueHint, appendValueSuggestions, deduplicateSuggestions } from "./suggestion.js";
4
5
  import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
5
6
 
6
7
  //#region src/valueparser.ts
8
+ function transformValueParserResult(result, mapping) {
9
+ if (!result.success) return result;
10
+ const preserveSnapshot = (mapped) => {
11
+ const snapshot = getSnapshottedDefaultDependencyValues(result);
12
+ return snapshot == null ? mapped : snapshotDefaultDependencyValues(mapped, snapshot);
13
+ };
14
+ if (result.deferred) try {
15
+ return preserveSnapshot({
16
+ success: true,
17
+ value: mapping.map(result.value),
18
+ deferred: true
19
+ });
20
+ } catch {
21
+ return preserveSnapshot({
22
+ success: true,
23
+ value: void 0,
24
+ deferred: true
25
+ });
26
+ }
27
+ return preserveSnapshot({
28
+ success: true,
29
+ value: mapping.map(result.value)
30
+ });
31
+ }
7
32
  /**
8
33
  * A predicate function that checks if an object is a {@link ValueParser}.
9
34
  * @param object The object to check.
@@ -191,6 +216,187 @@ function choice(choices, options = {}) {
191
216
  }
192
217
  };
193
218
  }
219
+ function biject(mapping) {
220
+ if (Array.isArray(mapping)) throw new TypeError("Expected biject mapping to be a non-array object.");
221
+ const keys = [];
222
+ for (const key in mapping) if (Object.prototype.hasOwnProperty.call(mapping, key)) keys.push(key);
223
+ if (keys.length < 1) throw new RangeError("Expected at least one biject entry.");
224
+ const source = choice(keys);
225
+ const forward = /* @__PURE__ */ new Map();
226
+ const reverse = /* @__PURE__ */ new Map();
227
+ for (const key of keys) {
228
+ const value = mapping[key];
229
+ if (reverse.has(value)) throw new RangeError(`Duplicate biject value for key ${JSON.stringify(key)}.`);
230
+ forward.set(key, value);
231
+ reverse.set(value, key);
232
+ }
233
+ const parser = transform(source, {
234
+ map(value) {
235
+ return forward.get(value);
236
+ },
237
+ unmap(value) {
238
+ const key = reverse.get(value);
239
+ if (key !== void 0) return key;
240
+ try {
241
+ return String(value);
242
+ } catch {
243
+ return "";
244
+ }
245
+ }
246
+ });
247
+ Object.defineProperty(parser, "validate", {
248
+ value(value) {
249
+ if (reverse.has(value)) return {
250
+ success: true,
251
+ value
252
+ };
253
+ let input;
254
+ try {
255
+ input = String(value);
256
+ } catch {
257
+ input = "";
258
+ }
259
+ const result = source.parse(input);
260
+ if (!result.success) return result;
261
+ return {
262
+ success: false,
263
+ error: formatDefaultChoiceError(input, keys)
264
+ };
265
+ },
266
+ configurable: true,
267
+ enumerable: true
268
+ });
269
+ return parser;
270
+ }
271
+ /**
272
+ * Creates a value parser that transforms the result of another value parser.
273
+ *
274
+ * This is useful when an existing value parser already describes the accepted
275
+ * CLI spelling, suggestions, and error messages, but your application wants a
276
+ * different result type. For example, a string `choice()` can be transformed
277
+ * into an internal enum, tagged object, or other domain type.
278
+ *
279
+ * Unlike parser-level `map()`, value parser transformation needs an inverse
280
+ * mapping. The `unmap` function lets the transformed parser format values,
281
+ * validate fallback/default values, and reuse the wrapped parser's metadata
282
+ * without guessing how to serialize the transformed type.
283
+ *
284
+ * Transform functions are synchronous. If the wrapped parser is async, the
285
+ * returned parser is async too, but `map` and `unmap` still run synchronously
286
+ * after the wrapped parser resolves.
287
+ *
288
+ * @template M The execution mode of the wrapped parser.
289
+ * @template T The value type produced by the wrapped parser.
290
+ * @template U The value type produced by the transformed parser.
291
+ * @param parser The value parser to transform.
292
+ * @param mapping Mapping functions between the wrapped and transformed value
293
+ * types.
294
+ * @returns A value parser that accepts the same input as `parser` and produces
295
+ * transformed values.
296
+ * @since 1.2.0
297
+ */
298
+ function transform(parser, mapping) {
299
+ const normalize = parser.normalize?.bind(parser);
300
+ const suggest = parser.suggest?.bind(parser);
301
+ const transformedChoices = parser.choices == null ? void 0 : Object.freeze(parser.choices.map((choice$1) => mapping.map(choice$1)));
302
+ const transformed = {
303
+ mode: parser.mode,
304
+ metavar: parser.metavar,
305
+ placeholder: void 0,
306
+ ...transformedChoices == null ? {} : { choices: transformedChoices },
307
+ parse(input) {
308
+ return mapModeValue(parser.mode, parser.parse(input), (result) => transformValueParserResult(result, mapping));
309
+ },
310
+ format(value) {
311
+ return parser.format(mapping.unmap(value));
312
+ },
313
+ ...normalize == null ? {} : { normalize(value) {
314
+ return mapping.map(normalize(mapping.unmap(value)));
315
+ } },
316
+ ...suggest == null ? {} : { suggest(prefix) {
317
+ return suggest(prefix);
318
+ } }
319
+ };
320
+ Object.defineProperty(transformed, "placeholder", {
321
+ get() {
322
+ try {
323
+ return mapping.map(parser.placeholder);
324
+ } catch {
325
+ return void 0;
326
+ }
327
+ },
328
+ configurable: true,
329
+ enumerable: false
330
+ });
331
+ if (typeof parser.validate === "function") {
332
+ const validate = parser.validate.bind(parser);
333
+ Object.defineProperty(transformed, "validate", {
334
+ value(value) {
335
+ const result = validate(mapping.unmap(value));
336
+ return result.success ? {
337
+ success: true,
338
+ value: mapping.map(result.value)
339
+ } : result;
340
+ },
341
+ configurable: true,
342
+ enumerable: true
343
+ });
344
+ } else if (parser.mode === "sync") {
345
+ const syncParser = parser;
346
+ Object.defineProperty(transformed, "validate", {
347
+ value(value) {
348
+ const result = syncParser.parse(syncParser.format(mapping.unmap(value)));
349
+ return result.success ? {
350
+ success: true,
351
+ value: mapping.map(result.value)
352
+ } : result;
353
+ },
354
+ configurable: true,
355
+ enumerable: true
356
+ });
357
+ }
358
+ if (isDerivedValueParser(parser)) preserveTransformedDerivedMetadata(transformed, parser, mapping);
359
+ return transformed;
360
+ }
361
+ function preserveTransformedDerivedMetadata(transformed, parser, mapping) {
362
+ Object.defineProperties(transformed, {
363
+ [derivedValueParserMarker]: {
364
+ value: true,
365
+ enumerable: true
366
+ },
367
+ [dependencyId]: {
368
+ value: parser[dependencyId],
369
+ enumerable: true
370
+ },
371
+ [parseWithDependency]: {
372
+ value(input, dependencyValue) {
373
+ return mapMaybePromiseByMode(parser.mode, parser[parseWithDependency](input, dependencyValue), (result) => transformValueParserResult(result, mapping));
374
+ },
375
+ enumerable: true
376
+ }
377
+ });
378
+ if (dependencyIds in parser && parser[dependencyIds] != null) Object.defineProperty(transformed, dependencyIds, {
379
+ value: parser[dependencyIds],
380
+ enumerable: true
381
+ });
382
+ if (defaultValues in parser && parser[defaultValues] != null) Object.defineProperty(transformed, defaultValues, {
383
+ value: parser[defaultValues],
384
+ enumerable: true
385
+ });
386
+ if (singleDefaultValue in parser && parser[singleDefaultValue] != null) Object.defineProperty(transformed, singleDefaultValue, {
387
+ value: parser[singleDefaultValue],
388
+ enumerable: true
389
+ });
390
+ if (suggestWithDependency in parser && parser[suggestWithDependency] != null) {
391
+ const suggest = parser[suggestWithDependency];
392
+ Object.defineProperty(transformed, suggestWithDependency, {
393
+ value(prefix, dependencyValue) {
394
+ return wrapIterableForMode(parser.mode, suggest(prefix, dependencyValue));
395
+ },
396
+ enumerable: true
397
+ });
398
+ }
399
+ }
194
400
  /**
195
401
  * Validates that an option value, if present, is a boolean.
196
402
  * Throws a {@link TypeError} if the value is defined but not a boolean.
@@ -6336,4 +6542,4 @@ function plainObjectsEqual(a, b) {
6336
6542
  }
6337
6543
 
6338
6544
  //#endregion
6339
- export { checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
6545
+ export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.2.0-dev.2298",
3
+ "version": "1.2.0-dev.2302",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -225,7 +225,7 @@
225
225
  "fast-check": "^4.7.0",
226
226
  "tsdown": "^0.13.0",
227
227
  "typescript": "^5.8.3",
228
- "@optique/env": "1.2.0-dev.2298+a0394b06"
228
+ "@optique/env": "1.2.0-dev.2302+77665cb5"
229
229
  },
230
230
  "scripts": {
231
231
  "build": "tsdown",