Summary
When we infer config types from configliere field maps, fields with schema defaults are still typed as optional (T | undefined) even though parsing guarantees they are present at runtime. This forces downstream code to add assertion helpers like assertDefault() just to convince TypeScript of invariants the parser already knows.
Problem
We currently have code like this in downstream consumers:
type RunParsedConfig = InferConfig<typeof runFields>;
where runFields contains defaults such as:
"show-browser": field(z.boolean().optional().default(false))
"timeout-immediate": field(z.number().optional().default(2000))
At runtime, after parse(...), these fields are present. But at the type level they remain:
"show-browser"?: boolean
"timeout-immediate"?: number
That mismatch forces code like:
'show-browser': assertDefault(config["show-browser"], "show-browser"),
'timeout-immediate': assertDefault(config["timeout-immediate"], "timeout-immediate"),
This is not a real runtime defaulting step. It is only a type-level assertion to bridge a gap between parser behavior and exposed types.
Why this is a problem
- Downstream code has to re-assert parser guarantees
- Conversion layers become noisy and misleading
- Defaults are defined in one place but effectively "re-proven" elsewhere
- Handwritten normalized config types can drift from field definitions
- Users of configliere lose confidence that parser output types reflect actual parse semantics
Desired behavior
After parsing, fields with defaults should be reflected in the output type as required values.
For example, if a parser contains:
field(z.boolean().optional().default(false))
field(z.number().optional().default(2000))
then the parsed output type should expose:
boolean, not boolean | undefined
number, not number | undefined
Ideal API direction
Any of these would solve the problem:
-
A parser-output type that reflects post-parse guarantees
Example shape:
const parser = object(runFields);
type Parsed = InferParsed<typeof parser>;
-
Stronger typing on parse(...).value so defaults are already represented in the result type
-
A normalization/output helper type exported by configliere that represents the schema after defaults are applied
Concrete downstream benefit
This would let downstream code simplify from:
'show-browser': assertDefault(config["show-browser"], "show-browser"),
'timeout-immediate': assertDefault(config["timeout-immediate"], "timeout-immediate"),
to:
'show-browser': config["show-browser"],
'timeout-immediate': config["timeout-immediate"],
and eliminate assertion helpers that exist only because the types are weaker than the runtime semantics.
Example use case
A CLI bootstrap parses command config, then passes it through a conversion layer into legacy command option types. Some fields are truly required by policy and still need validation (requireEnv()), but fields with parser defaults should not require additional assertions.
Expected outcome
Consumers should be able to rely on parser output types as the source of truth for:
- Requiredness after defaults
- Normalized scalar/array shape
- Downstream option conversion without assertion helpers for defaulted fields
Related context
This is closely related to the general goal of making configliere parser output types better reflect real parser behavior, especially for defaults and other normalization steps.
Summary
When we infer config types from configliere field maps, fields with schema defaults are still typed as optional (
T | undefined) even though parsing guarantees they are present at runtime. This forces downstream code to add assertion helpers likeassertDefault()just to convince TypeScript of invariants the parser already knows.Problem
We currently have code like this in downstream consumers:
where
runFieldscontains defaults such as:At runtime, after
parse(...), these fields are present. But at the type level they remain:"show-browser"?: boolean"timeout-immediate"?: numberThat mismatch forces code like:
This is not a real runtime defaulting step. It is only a type-level assertion to bridge a gap between parser behavior and exposed types.
Why this is a problem
Desired behavior
After parsing, fields with defaults should be reflected in the output type as required values.
For example, if a parser contains:
then the parsed output type should expose:
boolean, notboolean | undefinednumber, notnumber | undefinedIdeal API direction
Any of these would solve the problem:
A parser-output type that reflects post-parse guarantees
Example shape:
Stronger typing on
parse(...).valueso defaults are already represented in the result typeA normalization/output helper type exported by configliere that represents the schema after defaults are applied
Concrete downstream benefit
This would let downstream code simplify from:
to:
and eliminate assertion helpers that exist only because the types are weaker than the runtime semantics.
Example use case
A CLI bootstrap parses command config, then passes it through a conversion layer into legacy command option types. Some fields are truly required by policy and still need validation (
requireEnv()), but fields with parser defaults should not require additional assertions.Expected outcome
Consumers should be able to rely on parser output types as the source of truth for:
Related context
This is closely related to the general goal of making configliere parser output types better reflect real parser behavior, especially for defaults and other normalization steps.