vuelidify 2.0.3 → 2.1.1
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 +87 -16
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ Powerful and typed model-based validation for Vue 3
|
|
|
11
11
|
|
|
12
12
|
[Custom Validators](#custom-validators)
|
|
13
13
|
|
|
14
|
+
[Utility Functions](#utility-functions)
|
|
15
|
+
|
|
16
|
+
[Throttle Functions](#throttle-functions)
|
|
17
|
+
|
|
14
18
|
[Technical Details](#technical-details)
|
|
15
19
|
|
|
16
20
|
---
|
|
@@ -164,7 +168,7 @@ type BaseValidationReturn<F> = {
|
|
|
164
168
|
// Make sure your names are unique between your validators.
|
|
165
169
|
name?: string,
|
|
166
170
|
// the unique identifier for this validation result. Assigned internally.
|
|
167
|
-
// you can use this
|
|
171
|
+
// you can use this to identify your DOM elements that display error messages.
|
|
168
172
|
id? string,
|
|
169
173
|
// required for determining whether or not this validator passed
|
|
170
174
|
isValid: boolean,
|
|
@@ -177,18 +181,18 @@ type BaseValidationReturn<F> = {
|
|
|
177
181
|
```
|
|
178
182
|
Here is the breakdown of the parameters that are passed into validators
|
|
179
183
|
```ts
|
|
180
|
-
type ValidatorParams<T,
|
|
184
|
+
type ValidatorParams<T,KModel,Args,Ancestors> = {
|
|
181
185
|
// The value of the property being validated
|
|
182
186
|
value: T,
|
|
183
187
|
// The top-most ancestor being validated. The object that was passed to the composable.
|
|
184
|
-
|
|
188
|
+
model: KModel,
|
|
185
189
|
// The args that were specified in the composable configuration.
|
|
186
|
-
args:
|
|
190
|
+
args: Args,
|
|
187
191
|
// The type will be an ordered array of strongly typed objects.
|
|
188
192
|
// Each index is an ancestor to what you're validating.
|
|
189
193
|
// Index 0 will appear when you're 1 array deep, and index 1 will appear 2 arrays deep, etc.
|
|
190
194
|
// Extremely useful for complex validation.
|
|
191
|
-
arrayAncestors:
|
|
195
|
+
arrayAncestors: Ancestors
|
|
192
196
|
}
|
|
193
197
|
```
|
|
194
198
|
|
|
@@ -357,7 +361,7 @@ Sometimes your objects will contain objects and arrays.
|
|
|
357
361
|
</script>
|
|
358
362
|
```
|
|
359
363
|
### Validation State
|
|
360
|
-
|
|
364
|
+
In order to access the results of validation, access the `state` property returned from the composable. As long as you have TypeScript enabled in your workspace, you should have no problem understanding its layout. Validation state copies the layout of your model and puts state objects where values should be instead. Here's a short example on what you can do with the state object.
|
|
361
365
|
```ts
|
|
362
366
|
const v$ = useValidation({
|
|
363
367
|
// ... complex object validation provided earlier ...
|
|
@@ -367,7 +371,7 @@ const aAgeErrors: string[] | undefined = v$.state.a?.age?.$state?.errorMessages;
|
|
|
367
371
|
// An array of validation state objects on each person of property b.
|
|
368
372
|
const bErrors = v$.state.b?.$arrayState;
|
|
369
373
|
```
|
|
370
|
-
Note, properties may show up in the intellisense
|
|
374
|
+
Note, your properties may show up in the intellisense for `state`, but they are undefinable *on purpose*. If validation rules are not provided for a property, its state object will not exist.
|
|
371
375
|
|
|
372
376
|
# Provided Validators
|
|
373
377
|
Here are the validators that Vuelidify provides by default:
|
|
@@ -390,6 +394,9 @@ Here are the validators that Vuelidify provides by default:
|
|
|
390
394
|
- ```ts
|
|
391
395
|
must(fn: (params) => boolean, errorMessage: string)
|
|
392
396
|
```
|
|
397
|
+
- ```ts
|
|
398
|
+
validateIf(predicate: (params) => boolean | Promise<boolean>, validators: Validator[])
|
|
399
|
+
```
|
|
393
400
|
- ```ts
|
|
394
401
|
isEmailSync()
|
|
395
402
|
```
|
|
@@ -413,7 +420,7 @@ export function isEmailSync<
|
|
|
413
420
|
T extends string | undefined | null,
|
|
414
421
|
// The type for the model parameter.
|
|
415
422
|
// Generally you don't put constraints on this.
|
|
416
|
-
|
|
423
|
+
K,
|
|
417
424
|
// The type for the args
|
|
418
425
|
// You may want to put a constraint on this if you need access to a store, or some other external data.
|
|
419
426
|
V,
|
|
@@ -424,12 +431,12 @@ export function isEmailSync<
|
|
|
424
431
|
A
|
|
425
432
|
>(
|
|
426
433
|
// Specify any parameters you need here. This can be configuration (like a max length) or reactive variables.
|
|
427
|
-
): SyncValidator<T,
|
|
434
|
+
): SyncValidator<T, K, V, R, A> // Explicitly type the validator you'll be returning
|
|
428
435
|
{
|
|
429
436
|
// Return a validator function
|
|
430
437
|
return (
|
|
431
438
|
// Strongly type the expected params object to have intellisense
|
|
432
|
-
params: ValidatorParams<T,
|
|
439
|
+
params: ValidatorParams<T, K, V, A>
|
|
433
440
|
) => {
|
|
434
441
|
// you can do whatever you want a normal validator can in here.
|
|
435
442
|
// Return undefined, an array of validators, or a validation result.
|
|
@@ -458,6 +465,56 @@ export function isEmailSync<T extends string | undefined | null>(): SyncValidato
|
|
|
458
465
|
|
|
459
466
|
This validator is effectively: `SyncValidator<string | undefined | null, unknown, unknown, unknown, unknown>`
|
|
460
467
|
|
|
468
|
+
## Throttle Functions
|
|
469
|
+
Vuelidify provides several throttling functions for limiting how often a function can be invoked. We figured these would be useful outside of just validation. Functions like `debounce` and `throttle` are common examples exported by lodash. However, lodash's implementations are often hard to use because they don't return control back to the caller (i.e. they don't return a promise). The functions Vuelidify provides strongly type themselves to the function you provide, and always return a promise when it makes sense to. Here is a list of the available throttling functions:
|
|
470
|
+
|
|
471
|
+
- ```ts
|
|
472
|
+
bufferAsync<F extends (...args: any[]) => any>(
|
|
473
|
+
func: F,
|
|
474
|
+
): (...params: Parameters<F>) => Promise<Awaited<ReturnType<F>> | typeof IGNORE_RESULT>
|
|
475
|
+
```
|
|
476
|
+
`bufferAsync` ensures that the provided function has only one instance executing at a time. Calls to the buffered function will return a promise to execute when the current instance returns. Only the latest promise will execute the function next, all other promises will resolve to `IGNORE_RESULT` once the current instance returns. This function is very useful for only invoking resource-heavy functions while guaranteeing each execution uses the most up-to-date parameters.
|
|
477
|
+
|
|
478
|
+
- ```ts
|
|
479
|
+
throttleBufferAsync<F extends (...args: any[]) => any>(
|
|
480
|
+
func: F,
|
|
481
|
+
delayMs: number,
|
|
482
|
+
): (...params: Parameters<F>) => Promise<Awaited<ReturnType<F>> | typeof IGNORE_RESULT>
|
|
483
|
+
```
|
|
484
|
+
`throttleBufferAsync` behaves very similarly to `bufferAsync`, but instead of waiting for the current instance to return, it waits for a throttle duration to expire. Once the throttle expires the latest buffered promise will execute the function and all others will resolve to `IGNORE_RESULT`. This means multiple instances of the function could be running at the same time, depending on the throttle and how long the function takes to return.
|
|
485
|
+
|
|
486
|
+
- ```ts
|
|
487
|
+
throttleAsync<F extends (...args: any) => any>(
|
|
488
|
+
func: F,
|
|
489
|
+
delayMs: number,
|
|
490
|
+
): {
|
|
491
|
+
isThrottled: Ref<boolean>;
|
|
492
|
+
throttledFunc: (...params: Parameters<F>) => ReturnType<F> | typeof IGNORE_RESULT;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
`throttleAsync` ensures a function can only be invoked once every throttle period. Does not use buffering. Returns two objects, a ref indicating if the function is in its throttle period and the throttled function. Useful for hard limiting invocation of a function (e.g. forgot password form submission). Calls during the throttle period return `IGNORE_RESULT`.
|
|
496
|
+
|
|
497
|
+
- ```ts
|
|
498
|
+
trailingDebounceAsync<F extends (...args: any) => any>(
|
|
499
|
+
func: F,
|
|
500
|
+
delayMs: number,
|
|
501
|
+
): (...params: Parameters<F>) => Promise<Awaited<ReturnType<F>> | typeof IGNORE_RESULT>
|
|
502
|
+
```
|
|
503
|
+
`trailingDebounceAsync` ensures a function will only be invoked after a delay has passed since the last call. Guarantees the latest parameters will be executed. All calls prior to the latest will return `IGNORE_RESULT`.
|
|
504
|
+
|
|
505
|
+
`IGNORE_RESULT` is a constant unique symbol exported by Vuelidify that helps you to identify when invocations are ignored by a throttle function. This was done to make sure this unique state doesn't conflict with possible returns from your own functions.
|
|
506
|
+
|
|
507
|
+
## Utility Functions
|
|
508
|
+
Vuelidify provides a utility function you are free to use as well.
|
|
509
|
+
|
|
510
|
+
- ```ts
|
|
511
|
+
reduceUndefined<T, K = NonNullable<T>>(
|
|
512
|
+
array: T[],
|
|
513
|
+
getter: (value: T) => K | undefined | null,
|
|
514
|
+
): K[]
|
|
515
|
+
```
|
|
516
|
+
Used internally when collecting error messages from validator results. Removes undefined or null values from a mapped array. The getter defaults to selecting every element in the array, but you can provide your own to perform your own mapping.
|
|
517
|
+
|
|
461
518
|
## Technical Details
|
|
462
519
|
For those interested in the inner workings of the library without looking at the code:
|
|
463
520
|
|
|
@@ -465,16 +522,30 @@ For those interested in the inner workings of the library without looking at the
|
|
|
465
522
|
|
|
466
523
|
- Lazy validation is only performed only when the `validate()` function is called. However, `validate()` will also invoke all reactive validators to guarantee all validation results are up-to-date with the model. Properties or the model itself may be valid before ever calling `validate()` if there were no lazy validators provided, and all reactive validators were true (or again none specified).
|
|
467
524
|
|
|
468
|
-
- Validation results
|
|
525
|
+
- Validation results should appear as soon as possible. Synchronous validators shouldn’t wait for asynchronous ones to finish before showing errors. But things get complex when mixing lazy and reactive validators that run at different times or modify the same data. It’s even trickier when validators return other validators—sometimes inconsistently across runs. Vuelidify solves these problems with a robust validation system:
|
|
526
|
+
- Each validator gets a unique ID.
|
|
527
|
+
|
|
528
|
+
- All validators are run concurrently and their results are processed as they come back.
|
|
529
|
+
|
|
530
|
+
- Any child validators returned inherit the parent's ID with a unique suffix.
|
|
531
|
+
|
|
532
|
+
- All child validators are evaluated immediately and tracked within the origin validator.
|
|
533
|
+
|
|
534
|
+
- If a parent validator returns a different set of children in a future run, the ones that are no longer present are removed.
|
|
535
|
+
|
|
536
|
+
- Validation configs are objects which are created to store any necessary data to validate a value. The validation configs that are created are entirely dependent on the validation rules provided. These validation configs make heavy use of Vue's reactivity system to ensure references to your object and its nested properties stays up-to-date. These configs are not exposed to developers directly, but `state` and values like `isValidating` are determined using these configs. The use of these configs greatly improve readability and maintainability of Vuelidify.
|
|
537
|
+
|
|
538
|
+
- Validation configs are usually created as soon as the composable is invoked, but in the case of arrays, they must be created dynamically with the content of the array. There's some nuance to using the `$each` validation rule to validate arrays. Validation of arrays works by creating a matching array of validation configs for each element of the array. In the case of arrays of primitives, it is impossible to assign IDs to each element such that they can map to a validation config which was created. On the other hand, each object in an array can be modified with an ID mapping an object to a validation config. This is an important feature, because the validation state for an array is meant to match 1:1 with the array validated. However, what happens when the elements shift around? <br/>
|
|
539
|
+
When the indexes of objects are modified, Vuelidify reactively maps the objects to their validation config and returns the correctly ordered state. This is important specifically for *lazy* validators--which likely won't validate every time an index changes. So if error messages from lazy validation are on an object whose index moves, they need to follow that object around. This does not behave correctly with primitives; basically, only the index is used to map validation configs to values. **Make sure to NOT use lazy validation on primitives in arrays which can change indexes.**
|
|
469
540
|
|
|
470
|
-
-
|
|
541
|
+
- Both lazy and reactive validations track their own iteration ID. Before updating anything, iterations check if they're still the most recent. This prevents outdated runs from making state changes like setting the `isValidating` ref to false when the latest validation is actually still validating.
|
|
471
542
|
|
|
472
|
-
-
|
|
543
|
+
- When results are returned from validation, Vuelidify updates existing entries instead of replacing them. This avoids flickering or reordering in the results array—especially useful if you want to animate error messages smoothly.
|
|
473
544
|
|
|
474
|
-
-
|
|
545
|
+
- Async validators can be mixed with sync validators, so there is no way to distinguish them upon initialization. However, once they are invoked for the first time, it is possible to distinguish them. Optimizations can then be made on the synchronous validators to improve validation behavior and performance. Sync validators will be wrapped in a computed function which has the benefit of determining reactive dependencies and caching the result. This counteracts the downside of using a deep watcher discussed previously. Synchronous validators will not be needlessly reevaluated every time a character changes in an unrelated property because the computed determines its own reactive dependencies.
|
|
475
546
|
|
|
476
|
-
-
|
|
547
|
+
- **As of v2.1, all validators are guaranteed to run exclusively.** No validator will execute concurrently with itself. This removed the need for manual optimization of async-validators in earlier versions because all validators now essentially have a `bufferAsync` on them. This also has the added benefit of allowing developers to have piece of mind that their synchronous and asynchronous validators won't have weird race conditions because of concurrency. While a validator is being ran and its return is processed, all validation attempts are queued to happen after it returns. Before the buffered validations run, they check if they are the latest iteration, and if they aren't they return immediately. This means no matter how many times validation is invoked, only the latest state is validated. This decision for exclusivity was made because of a bug that arose around the `validateIf` validator, where race-conditions were happening with modifying the internal state of the validator to keep track of child validators it returned.
|
|
477
548
|
|
|
478
|
-
- This library uses `unknown` instead of `any` to align with Deno and strict TypeScript standards. While `Args` and `Ancestors` are logically `undefined` by default, using `undefined` as a type causes issues—`unknown` can't be assigned to `undefined`. This distinction is why some of Vuelidify’s types may seem unusual, especially when creating generic validators meant to work universally. Additionally, the default type of `Return` is `any` because it truly *can be* anything; if it was unknown, you
|
|
549
|
+
- This library uses `unknown` a lot instead of `any` to align with Deno and strict TypeScript standards. While the common generics, `Args` and `Ancestors` are logically `undefined` by default, using `undefined` as a type causes issues—`unknown` can't be assigned to `undefined`. This distinction is why some of Vuelidify’s types may seem unusual, especially when creating generic validators meant to work universally. Additionally, the default type of `Return` is `any` because it truly *can be* anything; if it was unknown, you wouldn't be able to access it from state. There are only a handful of spots in Vuelidify where any is used (e.g. throttle functions).
|
|
479
550
|
|
|
480
551
|
Feel free to post issues you may have with the package on the git repo! Happy validating!
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vuelidify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"author": "Daniel Walbolt",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,20 +19,20 @@
|
|
|
19
19
|
"url": "https://github.com/Daniel-Walbolt/vue-final-form"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"vue": "^3.
|
|
22
|
+
"vue": "^3.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@tsconfig/node20": "^20.1.
|
|
26
|
-
"@types/node": "^20.
|
|
27
|
-
"@vitejs/plugin-vue": "^5.
|
|
25
|
+
"@tsconfig/node20": "^20.1.5",
|
|
26
|
+
"@types/node": "^20.17.46",
|
|
27
|
+
"@vitejs/plugin-vue": "^5.2.4",
|
|
28
28
|
"@vue/eslint-config-typescript": "^13.0.0",
|
|
29
29
|
"@vue/tsconfig": "^0.5.1",
|
|
30
|
-
"eslint": "^8.57.
|
|
31
|
-
"eslint-plugin-vue": "^9.
|
|
32
|
-
"npm-run-all2": "^6.2.
|
|
30
|
+
"eslint": "^8.57.1",
|
|
31
|
+
"eslint-plugin-vue": "^9.33.0",
|
|
32
|
+
"npm-run-all2": "^6.2.6",
|
|
33
33
|
"tsup": "^6.7.0",
|
|
34
34
|
"typescript": "~5.4.5",
|
|
35
|
-
"vue-tsc": "^2.
|
|
35
|
+
"vue-tsc": "^2.2.10"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsup src/index.ts --format esm --dts --minify",
|