@tanstack/form-core 0.10.3 → 0.11.0

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.
Files changed (55) hide show
  1. package/build/legacy/FieldApi.cjs +91 -118
  2. package/build/legacy/FieldApi.cjs.map +1 -1
  3. package/build/legacy/FieldApi.d.cts +1 -2
  4. package/build/legacy/FieldApi.d.ts +1 -2
  5. package/build/legacy/FieldApi.js +91 -118
  6. package/build/legacy/FieldApi.js.map +1 -1
  7. package/build/legacy/FormApi.cjs +125 -118
  8. package/build/legacy/FormApi.cjs.map +1 -1
  9. package/build/legacy/FormApi.d.cts +1 -2
  10. package/build/legacy/FormApi.d.ts +1 -2
  11. package/build/legacy/FormApi.js +127 -118
  12. package/build/legacy/FormApi.js.map +1 -1
  13. package/build/legacy/index.d.cts +160 -73
  14. package/build/legacy/index.d.ts +160 -73
  15. package/build/legacy/types.cjs.map +1 -1
  16. package/build/legacy/types.d.cts +12 -3
  17. package/build/legacy/types.d.ts +12 -3
  18. package/build/legacy/utils.cjs +55 -0
  19. package/build/legacy/utils.cjs.map +1 -1
  20. package/build/legacy/utils.d.cts +3 -37
  21. package/build/legacy/utils.d.ts +3 -37
  22. package/build/legacy/utils.js +53 -0
  23. package/build/legacy/utils.js.map +1 -1
  24. package/build/modern/FieldApi.cjs +91 -116
  25. package/build/modern/FieldApi.cjs.map +1 -1
  26. package/build/modern/FieldApi.d.cts +1 -2
  27. package/build/modern/FieldApi.d.ts +1 -2
  28. package/build/modern/FieldApi.js +91 -116
  29. package/build/modern/FieldApi.js.map +1 -1
  30. package/build/modern/FormApi.cjs +125 -117
  31. package/build/modern/FormApi.cjs.map +1 -1
  32. package/build/modern/FormApi.d.cts +1 -2
  33. package/build/modern/FormApi.d.ts +1 -2
  34. package/build/modern/FormApi.js +127 -117
  35. package/build/modern/FormApi.js.map +1 -1
  36. package/build/modern/index.d.cts +160 -73
  37. package/build/modern/index.d.ts +160 -73
  38. package/build/modern/types.cjs.map +1 -1
  39. package/build/modern/types.d.cts +12 -3
  40. package/build/modern/types.d.ts +12 -3
  41. package/build/modern/utils.cjs +55 -0
  42. package/build/modern/utils.cjs.map +1 -1
  43. package/build/modern/utils.d.cts +3 -37
  44. package/build/modern/utils.d.ts +3 -37
  45. package/build/modern/utils.js +53 -0
  46. package/build/modern/utils.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/FieldApi.ts +308 -231
  49. package/src/FormApi.ts +259 -209
  50. package/src/tests/FieldApi.spec.ts +135 -48
  51. package/src/tests/FieldApi.test-d.ts +10 -6
  52. package/src/tests/FormApi.spec.ts +170 -62
  53. package/src/tests/utils.ts +1 -1
  54. package/src/types.ts +10 -2
  55. package/src/utils.ts +106 -0
package/src/FieldApi.ts CHANGED
@@ -1,115 +1,203 @@
1
1
  import { Store } from '@tanstack/store'
2
- import type { FormApi, ValidationErrorMap } from './FormApi'
3
- import type { ValidationError, Validator } from './types'
2
+ import type { FormApi } from './FormApi'
3
+ import type {
4
+ ValidationCause,
5
+ ValidationError,
6
+ ValidationErrorMap,
7
+ Validator,
8
+ } from './types'
4
9
  import type { DeepKeys, DeepValue, Updater } from './utils'
10
+ import { getAsyncValidatorArray, getSyncValidatorArray } from './utils'
5
11
 
6
- export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'
7
-
8
- type ValidateFn<
12
+ export type FieldValidateFn<
9
13
  TParentData,
10
14
  TName extends DeepKeys<TParentData>,
11
- ValidatorType,
15
+ TFieldValidator extends
16
+ | Validator<DeepValue<TParentData, TName>, unknown>
17
+ | undefined = undefined,
18
+ TFormValidator extends
19
+ | Validator<TParentData, unknown>
20
+ | undefined = undefined,
12
21
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
13
- > = (
14
- value: TData,
15
- fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
16
- ) => ValidationError
22
+ > = (props: {
23
+ value: TData
24
+ fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
25
+ }) => ValidationError
17
26
 
18
- type ValidateOrFn<
27
+ export type FieldValidateOrFn<
19
28
  TParentData,
20
29
  TName extends DeepKeys<TParentData>,
21
- ValidatorType,
22
- FormValidator,
30
+ TFieldValidator extends
31
+ | Validator<DeepValue<TParentData, TName>, unknown>
32
+ | undefined = undefined,
33
+ TFormValidator extends
34
+ | Validator<TParentData, unknown>
35
+ | undefined = undefined,
23
36
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
24
- > = ValidatorType extends Validator<TData>
37
+ > = TFieldValidator extends Validator<TData, infer TFN>
25
38
  ?
26
- | Parameters<ReturnType<ValidatorType>['validate']>[1]
27
- | ValidateFn<TParentData, TName, ValidatorType, TData>
28
- : FormValidator extends Validator<TData>
39
+ | TFN
40
+ | FieldValidateFn<
41
+ TParentData,
42
+ TName,
43
+ TFieldValidator,
44
+ TFormValidator,
45
+ TData
46
+ >
47
+ : TFormValidator extends Validator<TParentData, infer FFN>
29
48
  ?
30
- | Parameters<ReturnType<FormValidator>['validate']>[1]
31
- | ValidateFn<TParentData, TName, ValidatorType, TData>
32
- : ValidateFn<TParentData, TName, ValidatorType, TData>
33
-
34
- type ValidateAsyncFn<
49
+ | FFN
50
+ | FieldValidateFn<
51
+ TParentData,
52
+ TName,
53
+ TFieldValidator,
54
+ TFormValidator,
55
+ TData
56
+ >
57
+ : FieldValidateFn<TParentData, TName, TFieldValidator, TFormValidator, TData>
58
+
59
+ export type FieldValidateAsyncFn<
35
60
  TParentData,
36
61
  TName extends DeepKeys<TParentData>,
37
- ValidatorType,
62
+ TFieldValidator extends
63
+ | Validator<DeepValue<TParentData, TName>, unknown>
64
+ | undefined = undefined,
65
+ TFormValidator extends
66
+ | Validator<TParentData, unknown>
67
+ | undefined = undefined,
38
68
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
39
- > = (
40
- value: TData,
41
- fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
42
- ) => ValidationError | Promise<ValidationError>
69
+ > = (options: {
70
+ value: TData
71
+ fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
72
+ signal: AbortSignal
73
+ }) => ValidationError | Promise<ValidationError>
43
74
 
44
- type AsyncValidateOrFn<
75
+ export type FieldAsyncValidateOrFn<
45
76
  TParentData,
46
77
  TName extends DeepKeys<TParentData>,
47
- ValidatorType,
48
- FormValidator,
78
+ TFieldValidator extends
79
+ | Validator<DeepValue<TParentData, TName>, unknown>
80
+ | undefined = undefined,
81
+ TFormValidator extends
82
+ | Validator<TParentData, unknown>
83
+ | undefined = undefined,
49
84
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
50
- > = ValidatorType extends Validator<TData>
85
+ > = TFieldValidator extends Validator<TData, infer TFN>
51
86
  ?
52
- | Parameters<ReturnType<ValidatorType>['validate']>[1]
53
- | ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
54
- : FormValidator extends Validator<TData>
87
+ | TFN
88
+ | FieldValidateAsyncFn<
89
+ TParentData,
90
+ TName,
91
+ TFieldValidator,
92
+ TFormValidator,
93
+ TData
94
+ >
95
+ : TFormValidator extends Validator<TParentData, infer FFN>
55
96
  ?
56
- | Parameters<ReturnType<FormValidator>['validate']>[1]
57
- | ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
58
- : ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
97
+ | FFN
98
+ | FieldValidateAsyncFn<
99
+ TParentData,
100
+ TName,
101
+ TFieldValidator,
102
+ TFormValidator,
103
+ TData
104
+ >
105
+ : FieldValidateAsyncFn<
106
+ TParentData,
107
+ TName,
108
+ TFieldValidator,
109
+ TFormValidator,
110
+ TData
111
+ >
59
112
 
60
- export interface FieldOptions<
113
+ export interface FieldValidators<
61
114
  TParentData,
62
115
  TName extends DeepKeys<TParentData>,
63
- ValidatorType,
64
- FormValidator,
116
+ TFieldValidator extends
117
+ | Validator<DeepValue<TParentData, TName>, unknown>
118
+ | undefined = undefined,
119
+ TFormValidator extends
120
+ | Validator<TParentData, unknown>
121
+ | undefined = undefined,
65
122
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
66
123
  > {
67
- name: TName
68
- index?: TData extends any[] ? number : never
69
- defaultValue?: TData
70
- asyncDebounceMs?: number
71
- asyncAlways?: boolean
72
- preserveValue?: boolean
73
- validator?: ValidatorType
74
- onMount?: (
75
- formApi: FieldApi<TParentData, TName, ValidatorType, TData>,
76
- ) => void
77
- onChange?: ValidateOrFn<
124
+ onMount?: FieldValidateOrFn<
125
+ TParentData,
126
+ TName,
127
+ TFieldValidator,
128
+ TFormValidator,
129
+ TData
130
+ >
131
+ onChange?: FieldValidateOrFn<
78
132
  TParentData,
79
133
  TName,
80
- ValidatorType,
81
- FormValidator,
134
+ TFieldValidator,
135
+ TFormValidator,
82
136
  TData
83
137
  >
84
- onChangeAsync?: AsyncValidateOrFn<
138
+ onChangeAsync?: FieldAsyncValidateOrFn<
85
139
  TParentData,
86
140
  TName,
87
- ValidatorType,
88
- FormValidator,
141
+ TFieldValidator,
142
+ TFormValidator,
89
143
  TData
90
144
  >
91
145
  onChangeAsyncDebounceMs?: number
92
- onBlur?: ValidateOrFn<TParentData, TName, ValidatorType, FormValidator, TData>
93
- onBlurAsync?: AsyncValidateOrFn<
146
+ onBlur?: FieldValidateOrFn<
147
+ TParentData,
148
+ TName,
149
+ TFieldValidator,
150
+ TFormValidator,
151
+ TData
152
+ >
153
+ onBlurAsync?: FieldAsyncValidateOrFn<
94
154
  TParentData,
95
155
  TName,
96
- ValidatorType,
97
- FormValidator,
156
+ TFieldValidator,
157
+ TFormValidator,
98
158
  TData
99
159
  >
100
160
  onBlurAsyncDebounceMs?: number
101
- onSubmit?: ValidateOrFn<
161
+ onSubmit?: FieldValidateOrFn<
162
+ TParentData,
163
+ TName,
164
+ TFieldValidator,
165
+ TFormValidator,
166
+ TData
167
+ >
168
+ onSubmitAsync?: FieldAsyncValidateOrFn<
102
169
  TParentData,
103
170
  TName,
104
- ValidatorType,
105
- FormValidator,
171
+ TFieldValidator,
172
+ TFormValidator,
106
173
  TData
107
174
  >
108
- onSubmitAsync?: AsyncValidateOrFn<
175
+ onSubmitAsyncDebounceMs?: number
176
+ }
177
+
178
+ export interface FieldOptions<
179
+ TParentData,
180
+ TName extends DeepKeys<TParentData>,
181
+ TFieldValidator extends
182
+ | Validator<DeepValue<TParentData, TName>, unknown>
183
+ | undefined = undefined,
184
+ TFormValidator extends
185
+ | Validator<TParentData, unknown>
186
+ | undefined = undefined,
187
+ TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
188
+ > {
189
+ name: TName
190
+ index?: TData extends any[] ? number : never
191
+ defaultValue?: TData
192
+ asyncDebounceMs?: number
193
+ asyncAlways?: boolean
194
+ preserveValue?: boolean
195
+ validatorAdapter?: TFieldValidator
196
+ validators?: FieldValidators<
109
197
  TParentData,
110
198
  TName,
111
- ValidatorType,
112
- FormValidator,
199
+ TFieldValidator,
200
+ TFormValidator,
113
201
  TData
114
202
  >
115
203
  defaultMeta?: Partial<FieldMeta>
@@ -118,17 +206,21 @@ export interface FieldOptions<
118
206
  export interface FieldApiOptions<
119
207
  TParentData,
120
208
  TName extends DeepKeys<TParentData>,
121
- ValidatorType,
122
- FormValidator,
209
+ TFieldValidator extends
210
+ | Validator<DeepValue<TParentData, TName>, unknown>
211
+ | undefined = undefined,
212
+ TFormValidator extends
213
+ | Validator<TParentData, unknown>
214
+ | undefined = undefined,
123
215
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
124
216
  > extends FieldOptions<
125
217
  TParentData,
126
218
  TName,
127
- ValidatorType,
128
- FormValidator,
219
+ TFieldValidator,
220
+ TFormValidator,
129
221
  TData
130
222
  > {
131
- form: FormApi<TParentData, FormValidator>
223
+ form: FormApi<TParentData, TFormValidator>
132
224
  }
133
225
 
134
226
  export type FieldMeta = {
@@ -153,14 +245,30 @@ export type ResolveName<TParentData> = unknown extends TParentData
153
245
  export class FieldApi<
154
246
  TParentData,
155
247
  TName extends DeepKeys<TParentData>,
156
- ValidatorType,
157
- FormValidator,
248
+ TFieldValidator extends
249
+ | Validator<DeepValue<TParentData, TName>, unknown>
250
+ | undefined = undefined,
251
+ TFormValidator extends
252
+ | Validator<TParentData, unknown>
253
+ | undefined = undefined,
158
254
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
159
255
  > {
160
256
  uid: number
161
- form: FieldApiOptions<TParentData, TName, ValidatorType, TData>['form']
257
+ form: FieldApiOptions<
258
+ TParentData,
259
+ TName,
260
+ TFieldValidator,
261
+ TFormValidator,
262
+ TData
263
+ >['form']
162
264
  name!: DeepKeys<TParentData>
163
- options: FieldApiOptions<TParentData, TName, ValidatorType, TData> = {} as any
265
+ options: FieldApiOptions<
266
+ TParentData,
267
+ TName,
268
+ TFieldValidator,
269
+ TFormValidator,
270
+ TData
271
+ > = {} as any
164
272
  store!: Store<FieldState<TData>>
165
273
  state!: FieldState<TData>
166
274
  prevState!: FieldState<TData>
@@ -169,8 +277,8 @@ export class FieldApi<
169
277
  opts: FieldApiOptions<
170
278
  TParentData,
171
279
  TName,
172
- ValidatorType,
173
- FormValidator,
280
+ TFieldValidator,
281
+ TFormValidator,
174
282
  TData
175
283
  >,
176
284
  ) {
@@ -224,6 +332,29 @@ export class FieldApi<
224
332
  this.options = opts as never
225
333
  }
226
334
 
335
+ runValidator<
336
+ TValue extends { value: TData; fieldApi: FieldApi<any, any, any, any> },
337
+ TType extends 'validate' | 'validateAsync',
338
+ >(props: {
339
+ validate: TType extends 'validate'
340
+ ? FieldValidateOrFn<any, any, any, any>
341
+ : FieldAsyncValidateOrFn<any, any, any, any>
342
+ value: TValue
343
+ type: TType
344
+ }): ReturnType<ReturnType<Validator<any>>[TType]> {
345
+ const adapters = [
346
+ this.form.options.validatorAdapter,
347
+ this.options.validatorAdapter,
348
+ ] as const
349
+ for (const adapter of adapters) {
350
+ if (adapter && typeof props.validate !== 'function') {
351
+ return adapter()[props.type](props.value, props.validate) as never
352
+ }
353
+ }
354
+
355
+ return (props.validate as FieldValidateFn<any, any>)(props.value) as never
356
+ }
357
+
227
358
  mount = () => {
228
359
  const info = this.getInfo()
229
360
  info.instances[this.uid] = this as never
@@ -243,7 +374,25 @@ export class FieldApi<
243
374
  })
244
375
 
245
376
  this.update(this.options as never)
246
- this.options.onMount?.(this as never)
377
+ const { onMount } = this.options.validators || {}
378
+
379
+ if (onMount) {
380
+ const error = this.runValidator({
381
+ validate: onMount,
382
+ value: {
383
+ value: this.state.value,
384
+ fieldApi: this,
385
+ },
386
+ type: 'validate',
387
+ })
388
+ if (error) {
389
+ this.setMeta((prev) => ({
390
+ ...prev,
391
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
392
+ errorMap: { ...prev?.errorMap, onMount: error },
393
+ }))
394
+ }
395
+ }
247
396
 
248
397
  return () => {
249
398
  const preserveValue = this.options.preserveValue
@@ -260,7 +409,13 @@ export class FieldApi<
260
409
  }
261
410
 
262
411
  update = (
263
- opts: FieldApiOptions<TParentData, TName, ValidatorType, TData>,
412
+ opts: FieldApiOptions<
413
+ TParentData,
414
+ TName,
415
+ TFieldValidator,
416
+ TFormValidator,
417
+ TData
418
+ >,
264
419
  ) => {
265
420
  // Default Value
266
421
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -330,52 +485,20 @@ export class FieldApi<
330
485
  TSubData extends DeepValue<TData, TSubName> = DeepValue<TData, TSubName>,
331
486
  >(
332
487
  name: TSubName,
333
- ): FieldApi<TData, TSubName, ValidatorType, TSubData> =>
488
+ ): FieldApi<
489
+ TData,
490
+ TSubName,
491
+ Validator<TSubData, unknown> | undefined,
492
+ Validator<TData, unknown> | undefined,
493
+ TSubData
494
+ > =>
334
495
  new FieldApi({
335
496
  name: `${this.name}.${name}` as never,
336
497
  form: this.form,
337
498
  }) as any
338
499
 
339
500
  validateSync = (value = this.state.value, cause: ValidationCause) => {
340
- const { onChange, onBlur, onSubmit } = this.options
341
-
342
- const validates =
343
- // https://github.com/TanStack/form/issues/490
344
- cause === 'submit'
345
- ? ([
346
- { cause: 'change', validate: onChange },
347
- { cause: 'blur', validate: onBlur },
348
- { cause: 'submit', validate: onSubmit },
349
- ] as const)
350
- : cause === 'change'
351
- ? ([{ cause: 'change', validate: onChange }] as const)
352
- : ([{ cause: 'blur', validate: onBlur }] as const)
353
-
354
- // Use the validationCount for all field instances to
355
- // track freshness of the validation
356
- const validationCount = (this.getInfo().validationCount || 0) + 1
357
- this.getInfo().validationCount = validationCount
358
-
359
- const doValidate = (validate: (typeof validates)[number]['validate']) => {
360
- if (this.options.validator && typeof validate !== 'function') {
361
- return (this.options.validator as Validator<TData>)().validate(
362
- value,
363
- validate,
364
- )
365
- }
366
-
367
- if (this.form.options.validator && typeof validate !== 'function') {
368
- return (this.form.options.validator as Validator<TData>)().validate(
369
- value,
370
- validate,
371
- )
372
- }
373
-
374
- return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
375
- value,
376
- this as never,
377
- )
378
- }
501
+ const validates = getSyncValidatorArray(cause, this.options)
379
502
 
380
503
  // Needs type cast as eslint errantly believes this is always falsy
381
504
  let hasErrored = false as boolean
@@ -383,7 +506,13 @@ export class FieldApi<
383
506
  this.form.store.batch(() => {
384
507
  for (const validateObj of validates) {
385
508
  if (!validateObj.validate) continue
386
- const error = normalizeError(doValidate(validateObj.validate))
509
+ const error = normalizeError(
510
+ this.runValidator({
511
+ validate: validateObj.validate,
512
+ value: { value, fieldApi: this },
513
+ type: 'validate',
514
+ }),
515
+ )
387
516
  const errorMapKey = getErrorMapKey(validateObj.cause)
388
517
  if (this.state.meta.errorMap[errorMapKey] !== error) {
389
518
  this.setMeta((prev) => ({
@@ -419,130 +548,80 @@ export class FieldApi<
419
548
  }))
420
549
  }
421
550
 
422
- // If a sync error is encountered for the errorMapKey (eg. onChange), cancel any async validation
423
- if (hasErrored) {
424
- this.cancelValidateAsync()
425
- }
426
-
427
551
  return { hasErrored }
428
552
  }
429
553
 
430
- __leaseValidateAsync = () => {
431
- const count = (this.getInfo().validationAsyncCount || 0) + 1
432
- this.getInfo().validationAsyncCount = count
433
- return count
434
- }
435
-
436
- cancelValidateAsync = () => {
437
- // Lease a new validation count to ignore any pending validations
438
- this.__leaseValidateAsync()
439
- // Cancel any pending validation state
440
- this.setMeta((prev) => ({
441
- ...prev,
442
- isValidating: false,
443
- }))
444
- }
445
-
446
554
  validateAsync = async (value = this.state.value, cause: ValidationCause) => {
447
- const {
448
- onChangeAsync,
449
- onBlurAsync,
450
- onSubmitAsync,
451
- asyncDebounceMs,
452
- onBlurAsyncDebounceMs,
453
- onChangeAsyncDebounceMs,
454
- } = this.options
455
-
456
- const validate =
457
- cause === 'change'
458
- ? onChangeAsync
459
- : cause === 'submit'
460
- ? onSubmitAsync
461
- : onBlurAsync
462
- if (!validate) return []
463
- const debounceMs =
464
- cause === 'submit'
465
- ? 0
466
- : (cause === 'change'
467
- ? onChangeAsyncDebounceMs
468
- : onBlurAsyncDebounceMs) ??
469
- asyncDebounceMs ??
470
- 0
471
-
472
- if (this.state.meta.isValidating !== true) {
555
+ const validates = getAsyncValidatorArray(cause, this.options)
556
+
557
+ if (!this.state.meta.isValidating) {
473
558
  this.setMeta((prev) => ({ ...prev, isValidating: true }))
474
559
  }
475
560
 
476
- // Use the validationCount for all field instances to
477
- // track freshness of the validation
478
- const validationAsyncCount = this.__leaseValidateAsync()
479
-
480
- const checkLatest = () =>
481
- validationAsyncCount === this.getInfo().validationAsyncCount
482
-
483
- if (!this.getInfo().validationPromise) {
484
- this.getInfo().validationPromise = new Promise((resolve, reject) => {
485
- this.getInfo().validationResolve = resolve
486
- this.getInfo().validationReject = reject
487
- })
488
- }
561
+ /**
562
+ * We have to use a for loop and generate our promises this way, otherwise it won't be sync
563
+ * when there are no validators needed to be run
564
+ */
565
+ const promises: Promise<ValidationError | undefined>[] = []
489
566
 
490
- if (debounceMs > 0) {
491
- await new Promise((r) => setTimeout(r, debounceMs))
492
- }
567
+ for (const validateObj of validates) {
568
+ if (!validateObj.validate) continue
569
+ const key = getErrorMapKey(validateObj.cause)
570
+ const fieldValidatorMeta = this.getInfo().validationMetaMap[key]
493
571
 
494
- const doValidate = () => {
495
- if (this.options.validator && typeof validate !== 'function') {
496
- return (this.options.validator as Validator<TData>)().validateAsync(
497
- value,
498
- validate,
499
- )
500
- }
572
+ fieldValidatorMeta?.lastAbortController.abort()
573
+ // Sorry Safari 12
574
+ // eslint-disable-next-line compat/compat
575
+ const controller = new AbortController()
501
576
 
502
- if (this.form.options.validator && typeof validate !== 'function') {
503
- return (
504
- this.form.options.validator as Validator<TData>
505
- )().validateAsync(value, validate)
577
+ this.getInfo().validationMetaMap[key] = {
578
+ lastAbortController: controller,
506
579
  }
507
580
 
508
- return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
509
- value,
510
- this as never,
581
+ promises.push(
582
+ new Promise<ValidationError | undefined>(async (resolve) => {
583
+ let rawError!: ValidationError | undefined
584
+ try {
585
+ rawError = await new Promise((rawResolve, rawReject) => {
586
+ setTimeout(() => {
587
+ if (controller.signal.aborted) return rawResolve(undefined)
588
+ this.runValidator({
589
+ validate: validateObj.validate,
590
+ value: { value, fieldApi: this, signal: controller.signal },
591
+ type: 'validateAsync',
592
+ })
593
+ .then(rawResolve)
594
+ .catch(rawReject)
595
+ }, validateObj.debounceMs)
596
+ })
597
+ } catch (e: unknown) {
598
+ rawError = e as ValidationError
599
+ }
600
+ const error = normalizeError(rawError)
601
+ this.setMeta((prev) => {
602
+ return {
603
+ ...prev,
604
+ errorMap: {
605
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
606
+ ...prev?.errorMap,
607
+ [getErrorMapKey(cause)]: error,
608
+ },
609
+ }
610
+ })
611
+
612
+ resolve(error)
613
+ }),
511
614
  )
512
615
  }
513
616
 
514
- // Only kick off validation if this validation is the latest attempt
515
- if (checkLatest()) {
516
- const prevErrors = this.getMeta().errors
517
- try {
518
- const rawError = await doValidate()
519
- if (checkLatest()) {
520
- const error = normalizeError(rawError)
521
- this.setMeta((prev) => ({
522
- ...prev,
523
- isValidating: false,
524
- errorMap: {
525
- ...prev.errorMap,
526
- [getErrorMapKey(cause)]: error,
527
- },
528
- }))
529
- this.getInfo().validationResolve?.([...prevErrors, error])
530
- }
531
- } catch (error) {
532
- if (checkLatest()) {
533
- this.getInfo().validationReject?.([...prevErrors, error])
534
- throw error
535
- }
536
- } finally {
537
- if (checkLatest()) {
538
- this.setMeta((prev) => ({ ...prev, isValidating: false }))
539
- delete this.getInfo().validationPromise
540
- }
541
- }
617
+ let results: ValidationError[] = []
618
+ if (promises.length) {
619
+ results = await Promise.all(promises)
542
620
  }
543
621
 
544
- // Always return the latest validation promise to the caller
545
- return (await this.getInfo().validationPromise) ?? []
622
+ this.setMeta((prev) => ({ ...prev, isValidating: false }))
623
+
624
+ return results.filter(Boolean)
546
625
  }
547
626
 
548
627
  validate = (
@@ -559,10 +638,8 @@ export class FieldApi<
559
638
  // Attempt to sync validate first
560
639
  const { hasErrored } = this.validateSync(value, cause)
561
640
 
562
- if (hasErrored) {
563
- if (!this.options.asyncAlways) {
564
- return this.state.meta.errors
565
- }
641
+ if (hasErrored && !this.options.asyncAlways) {
642
+ return this.state.meta.errors
566
643
  }
567
644
  // No error? Attempt async validation
568
645
  return this.validateAsync(value, cause)