feat(validation): type inference for validators and schema builders - #17
Draft
halvardssm wants to merge 6 commits into
Draft
feat(validation): type inference for validators and schema builders#17halvardssm wants to merge 6 commits into
halvardssm wants to merge 6 commits into
Conversation
Implement Zod-inspired type inference for the validation namespace:
- Add `validation/infer.ts` with `InferInput`/`InferOutput` and composition
helpers (`InferMemberOutput`, `InferObjectOutput`,
`InferCombinationOutput`) that read `~standard.types`.
- Set `~standard.types` in the `schema()` builder so the inferred input/output
types resolve to the schema's `Input`/`Output` (the generic params alone cannot
infer `Input` since it is structurally absent from `StandardSchemaV1`).
- Make `array`, `object` and `combination` builders generic over their options
so composed types are inferred: `array({ items: string() })` -> `string[]`,
`object({ properties })` -> shape (all properties optional, since JSON
Schema's `required` is `string[]` and widens), `combination({ anyOf })` ->
union of member outputs.
- `validate`/`parse` accept `unknown` input (matching Zod's `parse(data:
unknown): Output`) and return `Result<InferOutput<S>>` / `InferOutput<S>`.
- Re-export `InferInput`/`InferOutput` (and helpers) from the package entrypoint.
- Add `validation/infer.test.ts` with compile-time `IsExact` assertions.
- Document type inference in `validation/README.md`.
Verified with `tsc` against the real `@standard-schema/spec` types and runtime
tests; existing behavior is preserved.
Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
JSON Schema objects may carry arbitrary keys (constrained at runtime by additionalProperties/unevaluatedProperties/patternProperties), so the inferred object output now includes a [key: string]: unknown index signature. This keeps declared property typing while preventing excess-property type errors in callers and tests that pass objects with additional keys. Add a compile-time test for it. Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
Extend array type inference to cover prefixItems, unevaluatedItems, and
contains, not just items:
- array({ items: string() }) -> string[]
- array({ prefixItems: [string(), number()] }) -> [string, number] (exact tuple)
- array({ prefixItems: [string()], items: number() }) -> [string, ...number[]]
- array({ prefixItems: [string()], unevaluatedItems: number() }) -> [string, ...number[]]
- array({ prefixItems: [string(), number()], contains: boolean() }) ->
[string, number, ...boolean[]]
- array({ contains: number() }) / array({ unevaluatedItems: number() }) -> T[]
- array() -> unknown[]
When prefixItems is present, the leading elements form a fixed tuple; when a
variadic rest source (items/unevaluatedItems/contains) is also present it is
appended as a variadic tail, otherwise the tuple is exact. Tuple preservation
relies on a `const Prefix` type parameter (TS 5.0+).
Add InferArrayTuple/InferArrayRest/InferArrayHasRest/InferArrayOutput helpers in
validation/infer.ts (re-exported from the package entrypoint), update the
array() builder to a two-generic signature, and add compile-time tests in
validation/infer.test.ts.
Verified locally with tsc (against the real @standard-schema/spec types), tsx,
deno fmt --check, and deno lint; existing runtime behavior is preserved.
Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
…dditionalProperties Extend object type inference to use `properties`, `required`, and `additionalProperties`: - Keys listed in `required` are required; the remaining declared keys are optional. - `additionalProperties: false` disallows extra (undeclared) keys (a `never` index signature forbids them); a schema or `true`/absent allows extras, typed `unknown` to avoid unsound conflicts with declared properties of a different type (the `additionalProperties` schema still validates extras at runtime). `required` is captured as a `const` tuple on the builder (TS 5.0+) so the literal keys are preserved rather than widened to `string[]`. Add InferObjectAdditionalIndex/InferObjectRequiredKeys helpers and a three-parameter InferObjectOutput in validation/infer.ts (re-exported from the package entrypoint), update the object() builder to a two-generic signature, and update compile-time tests in validation/infer.test.ts. Verified locally with tsc (against the real @standard-schema/spec types), tsx, deno fmt --check, and deno lint; existing runtime behavior is preserved. Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
- Replace runtime-dereferencing assertions in infer.test.ts (which crashed on
null) with IsSubtype type-level checks that have no runtime side effects.
- additionalProperties: false now contributes no index signature ({} ) rather
than a [key: string]: never index, which was unsound (it forbade declared
properties too). With no index signature, declared keys form a strict shape
and excess-property checks forbid undeclared keys on object literals.
Co-authored-by: halvardssm <halvardssm@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
validationnamespace.validation/infer.tswithInferInput/InferOutputplus composition helpers(
InferMemberOutput,InferObjectOutput,InferCombinationOutput) that readthe schema's
~standard.typesfield.schema()builder now sets~standard.types, so the inferred input/outputtypes resolve to the schema's
Input/Output(the generic params alone cannotinfer
Inputbecause it is structurally absent fromStandardSchemaV1).array,object, andcombinationbuilders are now generic over theiroptions, so composed types are inferred:
array({ items: string() })->string[]object({ properties })-> object shape (all properties optional, sinceJSON Schema's
requiredisstring[]and widens array literals)combination({ anyOf })-> union of member output typesvalidate/validateAsync/parse/parseAsyncacceptunknowninput(matching Zod's
parse(data: unknown): Output) and returnResult<InferOutput<S>>/InferOutput<S>instead ofunknown.InferInput/InferOutput(and helpers) from the package entrypoint.validation/infer.test.tswith compile-timeIsExactassertions forscalars,
array,object,combination, and theparse/validatesignatures.validation/README.md.Verification
validation/source + test files withtscagainst the real@standard-schema/spectype definitions (0 errors).--experimental-strip-types):validator.test.ts(12/12),utils.test.ts(14/14), and a broadjson_schema.test.tssubset covering scalars, formats, arrays (prefixItems,contains, uniqueItems), nested objects (additionalProperties, patternProperties,
minProperties), and combinations (allOf/anyOf/oneOf/not) — all pass with
behavior unchanged. New
infer.test.tsruntime sanity passes and itscompile-time assertions hold.