@optique/env 1.4.0-dev.2673 → 1.4.0-dev.2676

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/README.md CHANGED
@@ -76,6 +76,10 @@ Features
76
76
  - *.env file loading* without mutating `process.env` or `Deno.env`
77
77
  - *Custom env source* for Deno, tests, and custom runtimes
78
78
  - *Composable contexts* with `run()`/`runAsync()`/`runWith()`
79
+ - *Direct fallback reads* through `bindEnv()`'s `readFallback()` method,
80
+ useful when a CLI parse fails before returning a value. It reads only the
81
+ environment variable or default and returns a result, not a partial CLI
82
+ parse.
79
83
 
80
84
 
81
85
  Documentation
package/dist/index.cjs CHANGED
@@ -336,7 +336,8 @@ function createEnvContext(options = {}) {
336
336
  *
337
337
  * @param parser Parser that reads CLI values.
338
338
  * @param options Environment binding options.
339
- * @returns A parser with environment fallback behavior.
339
+ * @returns A parser with environment fallback behavior and a direct
340
+ * `readFallback()` method.
340
341
  * @throws {TypeError} If `key` is not a string or `parser` is not a valid
341
342
  * {@link ValueParser}.
342
343
  * @throws {Error} If the inner parser throws while parsing or completing a
@@ -523,10 +524,75 @@ function bindEnv(parser, options) {
523
524
  configurable: true,
524
525
  enumerable: false
525
526
  });
527
+ Object.defineProperty(boundParser, "readFallback", {
528
+ value: () => {
529
+ const sourceData = {
530
+ prefix: options.context.prefix,
531
+ source: options.context.source
532
+ };
533
+ const missing = {
534
+ success: false,
535
+ error: __optique_core_message.message`Missing required environment variable: ${(0, __optique_core_message.envVar)(`${sourceData.prefix}${options.key}`)}`
536
+ };
537
+ return (0, __optique_core_extension.dispatchByMode)(parser.mode, () => {
538
+ const result = getEnvFallback(options, parser.mode, sourceData, parser);
539
+ return result ?? missing;
540
+ }, async () => {
541
+ const result = getEnvFallback(options, parser.mode, sourceData, parser);
542
+ const awaited = await result;
543
+ return awaited ?? missing;
544
+ });
545
+ },
546
+ configurable: true,
547
+ enumerable: false
548
+ });
526
549
  (0, __optique_core_extension.delegateOptionParsing)(boundParser, parser, getInnerState);
527
550
  return (0, __optique_core_fluent.fluent)(boundParser);
528
551
  }
529
552
  /**
553
+ * Reads and validates the environment value or configured default.
554
+ * Returns `undefined` before mode dispatch when neither is available, so
555
+ * normal completion can still delegate to the wrapped parser.
556
+ */
557
+ function getEnvFallback(options, mode, sourceData, innerParser) {
558
+ const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
559
+ const rawValue = sourceData?.source(fullKey);
560
+ const validateSync = (parsed) => {
561
+ if (!parsed.success) return parsed;
562
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
563
+ return innerParser.validateValue(parsed.value);
564
+ };
565
+ const validateAsync = async (parsed) => {
566
+ if (!parsed.success) return parsed;
567
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
568
+ return await innerParser.validateValue(parsed.value);
569
+ };
570
+ if (rawValue !== void 0) {
571
+ if (typeof rawValue !== "string") {
572
+ const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
573
+ return (0, __optique_core_extension.wrapForMode)(mode, {
574
+ success: false,
575
+ error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
576
+ });
577
+ }
578
+ return (0, __optique_core_extension.dispatchByMode)(mode, () => {
579
+ const parsed = options.parser.parse(rawValue);
580
+ return validateSync(parsed);
581
+ }, async () => {
582
+ const parsed = await options.parser.parse(rawValue);
583
+ return await validateAsync(parsed);
584
+ });
585
+ }
586
+ if (options.default !== void 0) return (0, __optique_core_extension.dispatchByMode)(mode, () => validateSync({
587
+ success: true,
588
+ value: options.default
589
+ }), () => validateAsync({
590
+ success: true,
591
+ value: options.default
592
+ }));
593
+ return void 0;
594
+ }
595
+ /**
530
596
  * Resolves a `bindEnv()` fallback value with env > default > inner
531
597
  * `complete()` priority, running each candidate through the inner
532
598
  * parser's `validateValue()` hook when available so the inner CLI
@@ -565,42 +631,10 @@ function bindEnv(parser, options) {
565
631
  function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
566
632
  const annotations = (0, __optique_core_annotations.getAnnotations)(state);
567
633
  const sourceData = annotations?.[options.context.id];
568
- const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
569
- const rawValue = sourceData?.source(fullKey);
570
- const validateSync = (parsed) => {
571
- if (!parsed.success) return parsed;
572
- if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
573
- return innerParser.validateValue(parsed.value);
574
- };
575
- const validateAsync = async (parsed) => {
576
- if (!parsed.success) return parsed;
577
- if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
578
- return await innerParser.validateValue(parsed.value);
579
- };
580
- if (rawValue !== void 0) {
581
- if (typeof rawValue !== "string") {
582
- const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
583
- return (0, __optique_core_extension.wrapForMode)(mode, {
584
- success: false,
585
- error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
586
- });
587
- }
588
- return (0, __optique_core_extension.dispatchByMode)(mode, () => {
589
- const parsed = options.parser.parse(rawValue);
590
- return validateSync(parsed);
591
- }, async () => {
592
- const parsed = await options.parser.parse(rawValue);
593
- return await validateAsync(parsed);
594
- });
595
- }
596
- if (options.default !== void 0) return (0, __optique_core_extension.dispatchByMode)(mode, () => validateSync({
597
- success: true,
598
- value: options.default
599
- }), () => validateAsync({
600
- success: true,
601
- value: options.default
602
- }));
634
+ const fallback = getEnvFallback(options, mode, sourceData, innerParser);
635
+ if (fallback !== void 0) return fallback;
603
636
  const envContextAbsent = annotations != null && !(options.context.id in annotations);
637
+ const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
604
638
  if (innerParser != null) {
605
639
  const completeState = innerState ?? (annotations != null && innerParser.initialState == null && (0, __optique_core_extension.getTraits)(innerParser).inheritsAnnotations === true ? (0, __optique_core_extension.injectAnnotations)(innerParser.initialState, annotations) : innerParser.initialState);
606
640
  const innerResult = innerParser.complete(completeState, exec);
package/dist/index.d.cts CHANGED
@@ -2,8 +2,8 @@ import { HiddenVisibility } from "@optique/core/usage";
2
2
  import { SourceContext } from "@optique/core/context";
3
3
  import { FluentParser } from "@optique/core/fluent";
4
4
  import { Message } from "@optique/core/message";
5
- import { Mode, Parser } from "@optique/core/parser";
6
- import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
5
+ import { Mode, ModeValue, Parser } from "@optique/core/parser";
6
+ import { NonEmptyString, ValueParser, ValueParserResult } from "@optique/core/valueparser";
7
7
 
8
8
  //#region src/index.d.ts
9
9
 
@@ -158,6 +158,30 @@ interface BindEnvOptions<M extends Mode, TValue> {
158
158
  */
159
159
  readonly default?: TValue;
160
160
  }
161
+ /**
162
+ * A parser bound to an environment variable whose fallback can also be read
163
+ * independently of CLI parsing.
164
+ *
165
+ * @template M The execution mode of the parser.
166
+ * @template TValue The parser result value.
167
+ * @template TState The parser state.
168
+ * @since 1.4.0
169
+ */
170
+ type EnvBoundParser<M extends Mode, TValue, TState> = FluentParser<M, TValue, TState> & {
171
+ /**
172
+ * Reads this binding's environment value or configured default without
173
+ * reading CLI input or completing the wrapped parser.
174
+ *
175
+ * @returns A parsed fallback result, or a failure if the value is invalid
176
+ * or neither the environment nor a default supplies a value.
177
+ * Async bindings return a promise.
178
+ * @throws {Error} If the environment source, value parser, or wrapped
179
+ * parser's validation hook throws. Async bindings reject
180
+ * their promise instead.
181
+ * @since 1.4.0
182
+ */
183
+ readonly readFallback: () => ModeValue<M, ValueParserResult<TValue>>;
184
+ };
161
185
  /**
162
186
  * Binds a parser to environment variables with fallback behavior.
163
187
  *
@@ -188,7 +212,8 @@ interface BindEnvOptions<M extends Mode, TValue> {
188
212
  *
189
213
  * @param parser Parser that reads CLI values.
190
214
  * @param options Environment binding options.
191
- * @returns A parser with environment fallback behavior.
215
+ * @returns A parser with environment fallback behavior and a direct
216
+ * `readFallback()` method.
192
217
  * @throws {TypeError} If `key` is not a string or `parser` is not a valid
193
218
  * {@link ValueParser}.
194
219
  * @throws {Error} If the inner parser throws while parsing or completing a
@@ -201,7 +226,7 @@ interface BindEnvOptions<M extends Mode, TValue> {
201
226
  * CLI tokens are parsed (see issue #414).
202
227
  * @since 1.0.0
203
228
  */
204
- declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): FluentParser<M, TValue, TState>;
229
+ declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): EnvBoundParser<M, TValue, TState>;
205
230
  /**
206
231
  * Options for the {@link bool} parser.
207
232
  *
@@ -239,4 +264,4 @@ interface BoolOptions {
239
264
  */
240
265
  declare function bool(options?: BoolOptions): ValueParser<"sync", boolean>;
241
266
  //#endregion
242
- export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvFileOptions, EnvFilePaths, EnvFileSubstitute, EnvSource, bindEnv, bool, createEnvContext };
267
+ export { BindEnvOptions, BoolOptions, EnvBoundParser, EnvContext, EnvContextOptions, EnvFileOptions, EnvFilePaths, EnvFileSubstitute, EnvSource, bindEnv, bool, createEnvContext };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { HiddenVisibility } from "@optique/core/usage";
2
2
  import { FluentParser } from "@optique/core/fluent";
3
3
  import { Message } from "@optique/core/message";
4
- import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
4
+ import { NonEmptyString, ValueParser, ValueParserResult } from "@optique/core/valueparser";
5
5
  import { SourceContext } from "@optique/core/context";
6
- import { Mode, Parser } from "@optique/core/parser";
6
+ import { Mode, ModeValue, Parser } from "@optique/core/parser";
7
7
 
8
8
  //#region src/index.d.ts
9
9
 
@@ -158,6 +158,30 @@ interface BindEnvOptions<M extends Mode, TValue> {
158
158
  */
159
159
  readonly default?: TValue;
160
160
  }
161
+ /**
162
+ * A parser bound to an environment variable whose fallback can also be read
163
+ * independently of CLI parsing.
164
+ *
165
+ * @template M The execution mode of the parser.
166
+ * @template TValue The parser result value.
167
+ * @template TState The parser state.
168
+ * @since 1.4.0
169
+ */
170
+ type EnvBoundParser<M extends Mode, TValue, TState> = FluentParser<M, TValue, TState> & {
171
+ /**
172
+ * Reads this binding's environment value or configured default without
173
+ * reading CLI input or completing the wrapped parser.
174
+ *
175
+ * @returns A parsed fallback result, or a failure if the value is invalid
176
+ * or neither the environment nor a default supplies a value.
177
+ * Async bindings return a promise.
178
+ * @throws {Error} If the environment source, value parser, or wrapped
179
+ * parser's validation hook throws. Async bindings reject
180
+ * their promise instead.
181
+ * @since 1.4.0
182
+ */
183
+ readonly readFallback: () => ModeValue<M, ValueParserResult<TValue>>;
184
+ };
161
185
  /**
162
186
  * Binds a parser to environment variables with fallback behavior.
163
187
  *
@@ -188,7 +212,8 @@ interface BindEnvOptions<M extends Mode, TValue> {
188
212
  *
189
213
  * @param parser Parser that reads CLI values.
190
214
  * @param options Environment binding options.
191
- * @returns A parser with environment fallback behavior.
215
+ * @returns A parser with environment fallback behavior and a direct
216
+ * `readFallback()` method.
192
217
  * @throws {TypeError} If `key` is not a string or `parser` is not a valid
193
218
  * {@link ValueParser}.
194
219
  * @throws {Error} If the inner parser throws while parsing or completing a
@@ -201,7 +226,7 @@ interface BindEnvOptions<M extends Mode, TValue> {
201
226
  * CLI tokens are parsed (see issue #414).
202
227
  * @since 1.0.0
203
228
  */
204
- declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): FluentParser<M, TValue, TState>;
229
+ declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): EnvBoundParser<M, TValue, TState>;
205
230
  /**
206
231
  * Options for the {@link bool} parser.
207
232
  *
@@ -239,4 +264,4 @@ interface BoolOptions {
239
264
  */
240
265
  declare function bool(options?: BoolOptions): ValueParser<"sync", boolean>;
241
266
  //#endregion
242
- export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvFileOptions, EnvFilePaths, EnvFileSubstitute, EnvSource, bindEnv, bool, createEnvContext };
267
+ export { BindEnvOptions, BoolOptions, EnvBoundParser, EnvContext, EnvContextOptions, EnvFileOptions, EnvFilePaths, EnvFileSubstitute, EnvSource, bindEnv, bool, createEnvContext };
package/dist/index.js CHANGED
@@ -313,7 +313,8 @@ function createEnvContext(options = {}) {
313
313
  *
314
314
  * @param parser Parser that reads CLI values.
315
315
  * @param options Environment binding options.
316
- * @returns A parser with environment fallback behavior.
316
+ * @returns A parser with environment fallback behavior and a direct
317
+ * `readFallback()` method.
317
318
  * @throws {TypeError} If `key` is not a string or `parser` is not a valid
318
319
  * {@link ValueParser}.
319
320
  * @throws {Error} If the inner parser throws while parsing or completing a
@@ -500,10 +501,75 @@ function bindEnv(parser, options) {
500
501
  configurable: true,
501
502
  enumerable: false
502
503
  });
504
+ Object.defineProperty(boundParser, "readFallback", {
505
+ value: () => {
506
+ const sourceData = {
507
+ prefix: options.context.prefix,
508
+ source: options.context.source
509
+ };
510
+ const missing = {
511
+ success: false,
512
+ error: message`Missing required environment variable: ${envVar(`${sourceData.prefix}${options.key}`)}`
513
+ };
514
+ return dispatchByMode(parser.mode, () => {
515
+ const result = getEnvFallback(options, parser.mode, sourceData, parser);
516
+ return result ?? missing;
517
+ }, async () => {
518
+ const result = getEnvFallback(options, parser.mode, sourceData, parser);
519
+ const awaited = await result;
520
+ return awaited ?? missing;
521
+ });
522
+ },
523
+ configurable: true,
524
+ enumerable: false
525
+ });
503
526
  delegateOptionParsing(boundParser, parser, getInnerState);
504
527
  return fluent(boundParser);
505
528
  }
506
529
  /**
530
+ * Reads and validates the environment value or configured default.
531
+ * Returns `undefined` before mode dispatch when neither is available, so
532
+ * normal completion can still delegate to the wrapped parser.
533
+ */
534
+ function getEnvFallback(options, mode, sourceData, innerParser) {
535
+ const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
536
+ const rawValue = sourceData?.source(fullKey);
537
+ const validateSync = (parsed) => {
538
+ if (!parsed.success) return parsed;
539
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
540
+ return innerParser.validateValue(parsed.value);
541
+ };
542
+ const validateAsync = async (parsed) => {
543
+ if (!parsed.success) return parsed;
544
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
545
+ return await innerParser.validateValue(parsed.value);
546
+ };
547
+ if (rawValue !== void 0) {
548
+ if (typeof rawValue !== "string") {
549
+ const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
550
+ return wrapForMode(mode, {
551
+ success: false,
552
+ error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
553
+ });
554
+ }
555
+ return dispatchByMode(mode, () => {
556
+ const parsed = options.parser.parse(rawValue);
557
+ return validateSync(parsed);
558
+ }, async () => {
559
+ const parsed = await options.parser.parse(rawValue);
560
+ return await validateAsync(parsed);
561
+ });
562
+ }
563
+ if (options.default !== void 0) return dispatchByMode(mode, () => validateSync({
564
+ success: true,
565
+ value: options.default
566
+ }), () => validateAsync({
567
+ success: true,
568
+ value: options.default
569
+ }));
570
+ return void 0;
571
+ }
572
+ /**
507
573
  * Resolves a `bindEnv()` fallback value with env > default > inner
508
574
  * `complete()` priority, running each candidate through the inner
509
575
  * parser's `validateValue()` hook when available so the inner CLI
@@ -542,42 +608,10 @@ function bindEnv(parser, options) {
542
608
  function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
543
609
  const annotations = getAnnotations(state);
544
610
  const sourceData = annotations?.[options.context.id];
545
- const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
546
- const rawValue = sourceData?.source(fullKey);
547
- const validateSync = (parsed) => {
548
- if (!parsed.success) return parsed;
549
- if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
550
- return innerParser.validateValue(parsed.value);
551
- };
552
- const validateAsync = async (parsed) => {
553
- if (!parsed.success) return parsed;
554
- if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
555
- return await innerParser.validateValue(parsed.value);
556
- };
557
- if (rawValue !== void 0) {
558
- if (typeof rawValue !== "string") {
559
- const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
560
- return wrapForMode(mode, {
561
- success: false,
562
- error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
563
- });
564
- }
565
- return dispatchByMode(mode, () => {
566
- const parsed = options.parser.parse(rawValue);
567
- return validateSync(parsed);
568
- }, async () => {
569
- const parsed = await options.parser.parse(rawValue);
570
- return await validateAsync(parsed);
571
- });
572
- }
573
- if (options.default !== void 0) return dispatchByMode(mode, () => validateSync({
574
- success: true,
575
- value: options.default
576
- }), () => validateAsync({
577
- success: true,
578
- value: options.default
579
- }));
611
+ const fallback = getEnvFallback(options, mode, sourceData, innerParser);
612
+ if (fallback !== void 0) return fallback;
580
613
  const envContextAbsent = annotations != null && !(options.context.id in annotations);
614
+ const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
581
615
  if (innerParser != null) {
582
616
  const completeState = innerState ?? (annotations != null && innerParser.initialState == null && getTraits(innerParser).inheritsAnnotations === true ? injectAnnotations(innerParser.initialState, annotations) : innerParser.initialState);
583
617
  const innerResult = innerParser.complete(completeState, exec);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/env",
3
- "version": "1.4.0-dev.2673",
3
+ "version": "1.4.0-dev.2676",
4
4
  "description": "Environment variable support for Optique",
5
5
  "keywords": [
6
6
  "CLI",
@@ -60,10 +60,10 @@
60
60
  },
61
61
  "sideEffects": false,
62
62
  "dependencies": {
63
- "@optique/core": "1.4.0-dev.2673+f2b98759"
63
+ "@optique/core": "1.4.0-dev.2676+e0287473"
64
64
  },
65
65
  "devDependencies": {
66
- "@optique/config": "1.4.0-dev.2673+f2b98759",
66
+ "@optique/config": "1.4.0-dev.2676+e0287473",
67
67
  "@types/node": "^24.0.0",
68
68
  "fast-check": "^4.7.0",
69
69
  "tsdown": "^0.13.0",