vuelidify 2.0.3 → 2.1.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.
- package/README.md +44 -15
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Powerful and typed model-based validation for Vue 3
|
|
|
11
11
|
|
|
12
12
|
[Custom Validators](#custom-validators)
|
|
13
13
|
|
|
14
|
+
[Throttle Functions](#throttle-functions)
|
|
15
|
+
|
|
14
16
|
[Technical Details](#technical-details)
|
|
15
17
|
|
|
16
18
|
---
|
|
@@ -177,18 +179,18 @@ type BaseValidationReturn<F> = {
|
|
|
177
179
|
```
|
|
178
180
|
Here is the breakdown of the parameters that are passed into validators
|
|
179
181
|
```ts
|
|
180
|
-
type ValidatorParams<T,
|
|
182
|
+
type ValidatorParams<T,KModel,Args,Ancestors> = {
|
|
181
183
|
// The value of the property being validated
|
|
182
184
|
value: T,
|
|
183
185
|
// The top-most ancestor being validated. The object that was passed to the composable.
|
|
184
|
-
|
|
186
|
+
model: KModel,
|
|
185
187
|
// The args that were specified in the composable configuration.
|
|
186
|
-
args:
|
|
188
|
+
args: Args,
|
|
187
189
|
// The type will be an ordered array of strongly typed objects.
|
|
188
190
|
// Each index is an ancestor to what you're validating.
|
|
189
191
|
// Index 0 will appear when you're 1 array deep, and index 1 will appear 2 arrays deep, etc.
|
|
190
192
|
// Extremely useful for complex validation.
|
|
191
|
-
arrayAncestors:
|
|
193
|
+
arrayAncestors: Ancestors
|
|
192
194
|
}
|
|
193
195
|
```
|
|
194
196
|
|
|
@@ -357,7 +359,7 @@ Sometimes your objects will contain objects and arrays.
|
|
|
357
359
|
</script>
|
|
358
360
|
```
|
|
359
361
|
### Validation State
|
|
360
|
-
|
|
362
|
+
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
363
|
```ts
|
|
362
364
|
const v$ = useValidation({
|
|
363
365
|
// ... complex object validation provided earlier ...
|
|
@@ -367,7 +369,7 @@ const aAgeErrors: string[] | undefined = v$.state.a?.age?.$state?.errorMessages;
|
|
|
367
369
|
// An array of validation state objects on each person of property b.
|
|
368
370
|
const bErrors = v$.state.b?.$arrayState;
|
|
369
371
|
```
|
|
370
|
-
Note, properties may show up in the intellisense
|
|
372
|
+
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
373
|
|
|
372
374
|
# Provided Validators
|
|
373
375
|
Here are the validators that Vuelidify provides by default:
|
|
@@ -390,6 +392,9 @@ Here are the validators that Vuelidify provides by default:
|
|
|
390
392
|
- ```ts
|
|
391
393
|
must(fn: (params) => boolean, errorMessage: string)
|
|
392
394
|
```
|
|
395
|
+
- ```ts
|
|
396
|
+
validateIf(predicate: (params) => boolean | Promise<boolean>, validators: Validator[])
|
|
397
|
+
```
|
|
393
398
|
- ```ts
|
|
394
399
|
isEmailSync()
|
|
395
400
|
```
|
|
@@ -413,7 +418,7 @@ export function isEmailSync<
|
|
|
413
418
|
T extends string | undefined | null,
|
|
414
419
|
// The type for the model parameter.
|
|
415
420
|
// Generally you don't put constraints on this.
|
|
416
|
-
|
|
421
|
+
K,
|
|
417
422
|
// The type for the args
|
|
418
423
|
// You may want to put a constraint on this if you need access to a store, or some other external data.
|
|
419
424
|
V,
|
|
@@ -424,12 +429,12 @@ export function isEmailSync<
|
|
|
424
429
|
A
|
|
425
430
|
>(
|
|
426
431
|
// Specify any parameters you need here. This can be configuration (like a max length) or reactive variables.
|
|
427
|
-
): SyncValidator<T,
|
|
432
|
+
): SyncValidator<T, K, V, R, A> // Explicitly type the validator you'll be returning
|
|
428
433
|
{
|
|
429
434
|
// Return a validator function
|
|
430
435
|
return (
|
|
431
436
|
// Strongly type the expected params object to have intellisense
|
|
432
|
-
params: ValidatorParams<T,
|
|
437
|
+
params: ValidatorParams<T, K, V, A>
|
|
433
438
|
) => {
|
|
434
439
|
// you can do whatever you want a normal validator can in here.
|
|
435
440
|
// Return undefined, an array of validators, or a validation result.
|
|
@@ -458,6 +463,16 @@ export function isEmailSync<T extends string | undefined | null>(): SyncValidato
|
|
|
458
463
|
|
|
459
464
|
This validator is effectively: `SyncValidator<string | undefined | null, unknown, unknown, unknown, unknown>`
|
|
460
465
|
|
|
466
|
+
## Throttle Functions
|
|
467
|
+
Vuelidify provides several throttling functions for limiting how often a function can be invoked. Internally, Vuelidify uses some of these internally to optimize async validators, but we figured they could 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:
|
|
468
|
+
|
|
469
|
+
- ```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.
|
|
470
|
+
- ```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 actually takes to return.
|
|
471
|
+
- ```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). Does not guarantee the latest invocation will be executed because invocations during the throttle period return `IGNORE_RESULT`.
|
|
472
|
+
- ```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`.
|
|
473
|
+
|
|
474
|
+
`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.
|
|
475
|
+
|
|
461
476
|
## Technical Details
|
|
462
477
|
For those interested in the inner workings of the library without looking at the code:
|
|
463
478
|
|
|
@@ -465,16 +480,30 @@ For those interested in the inner workings of the library without looking at the
|
|
|
465
480
|
|
|
466
481
|
- 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
482
|
|
|
468
|
-
- Validation results
|
|
483
|
+
- 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:
|
|
484
|
+
- Each validator gets a unique ID.
|
|
485
|
+
|
|
486
|
+
- All validators are run concurrently and their results are processed as they come back.
|
|
487
|
+
|
|
488
|
+
- Any child validators returned inherit the parent's ID with a unique suffix.
|
|
489
|
+
|
|
490
|
+
- All child validators are evaluated immediately and tracked within the origin validator.
|
|
491
|
+
|
|
492
|
+
- If a parent validator returns a different set of children in a future run, the ones that are no longer present are removed.
|
|
493
|
+
|
|
494
|
+
- 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.
|
|
495
|
+
|
|
496
|
+
- 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/>
|
|
497
|
+
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
498
|
|
|
470
|
-
-
|
|
499
|
+
- 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
500
|
|
|
472
|
-
-
|
|
501
|
+
- 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
502
|
|
|
474
|
-
-
|
|
503
|
+
- 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
504
|
|
|
476
|
-
-
|
|
505
|
+
- **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
506
|
|
|
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
|
|
507
|
+
- 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
508
|
|
|
480
509
|
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.0
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|