From 4c9ab954ee62040287fd17f3bea82b35b653d3ff Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:22:22 +0200 Subject: [PATCH 1/2] fix: tighten schema output type --- packages/form-core/src/validation.public.ts | 17 ++++- packages/form-core/tests/validation.test-d.ts | 66 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/form-core/src/validation.public.ts b/packages/form-core/src/validation.public.ts index 655a4621d5..457fb865ed 100644 --- a/packages/form-core/src/validation.public.ts +++ b/packages/form-core/src/validation.public.ts @@ -636,6 +636,19 @@ type TryGetSchemaOutput = TValidator extends { ? TOutput : undefined +type TryGetSubmitSchemaOutput = + TryGetSchemaOutput extends infer TOutput + ? TValidator extends { readonly runOnSubmit: infer TRunOnSubmit } + ? [TRunOnSubmit] extends [false] + ? undefined // User explicitly set runOnSubmit: false -> guaranteed undefined + : TRunOnSubmit extends (...args: Array) => boolean + ? TOutput | undefined // Callback could dynamically be true or false -> union + : false extends TRunOnSubmit + ? TOutput | undefined // the explicit variable is boolean, so also dynamic -> union + : TOutput + : TOutput // default, which is guaranteed present + : never + type ValidatorTriggers = TValidator extends { readonly triggers: infer TTriggers } @@ -667,9 +680,7 @@ type MappedSchemaOutputs> = { [K in keyof TValidators]: TValidators[K] extends { readonly run: any } - ? TValidators[K] extends { readonly runOnSubmit: false } - ? undefined - : TryGetSchemaOutput + ? TryGetSubmitSchemaOutput : never } diff --git a/packages/form-core/tests/validation.test-d.ts b/packages/form-core/tests/validation.test-d.ts index bc1b8c0b9d..71b099acab 100644 --- a/packages/form-core/tests/validation.test-d.ts +++ b/packages/form-core/tests/validation.test-d.ts @@ -1285,6 +1285,72 @@ describe('validator type transforms', () => { >() }) + it('makes schema outputs optional when runOnSubmit is dynamic', () => { + const dynamicRunOnSubmit = true as boolean + const vs = defineFormValidators([ + { + run: z.object({ name: z.string() }), + triggers: [], + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: true, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: false, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: dynamicRunOnSubmit, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: () => true, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: () => false, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: () => dynamicRunOnSubmit, + }, + { + run: z.object({ name: z.string() }), + triggers: [], + runOnSubmit: undefined, + }, + ]) + + type Output = { name: string } + type Outputs = ToFormSchemaOutputs + type Expected = readonly [ + // An omitted runOnSubmit defaults to true, so the output is guaranteed. + Output, + // A literal true always runs the validator during submit. + Output, + // A literal false always skips the validator during submit. + undefined, + // A broad boolean may skip the validator at runtime. + Output | undefined, + // Predicates currently always assume dynamic results. Types are here if you plan to tighten it in the future. + Output | undefined, + Output | undefined, + Output | undefined, + // An explicit undefined receives the same true default as an omitted property. + Output, + ] + + expectTypeOf().toEqualTypeOf() + }) + it('should transform field validators', () => { const vs = defineFieldValidators([ { From 504e0efec505d409a91f9c8ce21d4d6fffee91f5 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:44:02 +0200 Subject: [PATCH 2/2] chore: add changeset --- .changeset/odd-comics-push.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/odd-comics-push.md diff --git a/.changeset/odd-comics-push.md b/.changeset/odd-comics-push.md new file mode 100644 index 0000000000..25de60ef11 --- /dev/null +++ b/.changeset/odd-comics-push.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Fix: Schema output type now properly guards against dynamically enabled validators