vuelidify 2.0.2 → 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 +91 -28
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -7,10 +7,14 @@ Powerful and typed model-based validation for Vue 3
|
|
|
7
7
|
|
|
8
8
|
[Examples](#examples)
|
|
9
9
|
|
|
10
|
-
[
|
|
10
|
+
[Provided Validators](#provided-validators)
|
|
11
11
|
|
|
12
12
|
[Custom Validators](#custom-validators)
|
|
13
13
|
|
|
14
|
+
[Throttle Functions](#throttle-functions)
|
|
15
|
+
|
|
16
|
+
[Technical Details](#technical-details)
|
|
17
|
+
|
|
14
18
|
---
|
|
15
19
|
|
|
16
20
|
This library was inspired by Vuelidate and sought to solve some of its biggest problems. This library does NOT support Vue2, and does NOT support commonJS. Technology must move forward.
|
|
@@ -175,18 +179,18 @@ type BaseValidationReturn<F> = {
|
|
|
175
179
|
```
|
|
176
180
|
Here is the breakdown of the parameters that are passed into validators
|
|
177
181
|
```ts
|
|
178
|
-
type ValidatorParams<T,
|
|
182
|
+
type ValidatorParams<T,KModel,Args,Ancestors> = {
|
|
179
183
|
// The value of the property being validated
|
|
180
184
|
value: T,
|
|
181
185
|
// The top-most ancestor being validated. The object that was passed to the composable.
|
|
182
|
-
|
|
186
|
+
model: KModel,
|
|
183
187
|
// The args that were specified in the composable configuration.
|
|
184
|
-
args:
|
|
188
|
+
args: Args,
|
|
185
189
|
// The type will be an ordered array of strongly typed objects.
|
|
186
190
|
// Each index is an ancestor to what you're validating.
|
|
187
191
|
// Index 0 will appear when you're 1 array deep, and index 1 will appear 2 arrays deep, etc.
|
|
188
192
|
// Extremely useful for complex validation.
|
|
189
|
-
arrayAncestors:
|
|
193
|
+
arrayAncestors: Ancestors
|
|
190
194
|
}
|
|
191
195
|
```
|
|
192
196
|
|
|
@@ -355,7 +359,7 @@ Sometimes your objects will contain objects and arrays.
|
|
|
355
359
|
</script>
|
|
356
360
|
```
|
|
357
361
|
### Validation State
|
|
358
|
-
|
|
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.
|
|
359
363
|
```ts
|
|
360
364
|
const v$ = useValidation({
|
|
361
365
|
// ... complex object validation provided earlier ...
|
|
@@ -365,25 +369,42 @@ const aAgeErrors: string[] | undefined = v$.state.a?.age?.$state?.errorMessages;
|
|
|
365
369
|
// An array of validation state objects on each person of property b.
|
|
366
370
|
const bErrors = v$.state.b?.$arrayState;
|
|
367
371
|
```
|
|
368
|
-
Note, properties may show up in the intellisense
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
-
|
|
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.
|
|
373
|
+
|
|
374
|
+
# Provided Validators
|
|
375
|
+
Here are the validators that Vuelidify provides by default:
|
|
376
|
+
|
|
377
|
+
- ```ts
|
|
378
|
+
required()
|
|
379
|
+
```
|
|
380
|
+
- ```ts
|
|
381
|
+
minLength(min: number)
|
|
382
|
+
```
|
|
383
|
+
- ```ts
|
|
384
|
+
maxLength(max: number)
|
|
385
|
+
```
|
|
386
|
+
- ```ts
|
|
387
|
+
minNumber(min: number)
|
|
388
|
+
```
|
|
389
|
+
- ```ts
|
|
390
|
+
maxNumber(max: number)
|
|
391
|
+
```
|
|
392
|
+
- ```ts
|
|
393
|
+
must(fn: (params) => boolean, errorMessage: string)
|
|
394
|
+
```
|
|
395
|
+
- ```ts
|
|
396
|
+
validateIf(predicate: (params) => boolean | Promise<boolean>, validators: Validator[])
|
|
397
|
+
```
|
|
398
|
+
- ```ts
|
|
399
|
+
isEmailSync()
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
There aren't many validators provided by this library on purpose. If you feel a validator would be useful for everyone to have, give us feedback on our GitHub repository. However, we highly encourage you to understand how to make your own!
|
|
380
403
|
|
|
381
404
|
# Custom Validators
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
I highly encourage you to understand the types enough to create your own validators. It isn't too difficult, and you should be able to base your validator off of existing ones.
|
|
405
|
+
This section guides you to create your own generic validators. Validators were designed to be easy to create and easier to use.
|
|
385
406
|
|
|
386
|
-
Here is a breakdown of one of the validators
|
|
407
|
+
Here is a breakdown of one of the built-in validators (expanded to make comments more readable):
|
|
387
408
|
|
|
388
409
|
```ts
|
|
389
410
|
// always provide a header comment to explain what the validator does!
|
|
@@ -397,24 +418,23 @@ export function isEmailSync<
|
|
|
397
418
|
T extends string | undefined | null,
|
|
398
419
|
// The type for the model parameter.
|
|
399
420
|
// Generally you don't put constraints on this.
|
|
400
|
-
|
|
421
|
+
K,
|
|
401
422
|
// The type for the args
|
|
402
423
|
// You may want to put a constraint on this if you need access to a store, or some other external data.
|
|
403
424
|
V,
|
|
404
425
|
// The type for the custom return from the validator
|
|
405
426
|
R,
|
|
406
|
-
// The type for the
|
|
427
|
+
// The type for the arrayAncestors parameter.
|
|
407
428
|
// Generally you don't put constraints on this.
|
|
408
|
-
// But you have to accept this generic in order to pass the type forward to not mess up outside types.
|
|
409
429
|
A
|
|
410
430
|
>(
|
|
411
|
-
// Specify any parameters you need here. This
|
|
412
|
-
): SyncValidator<T,
|
|
431
|
+
// Specify any parameters you need here. This can be configuration (like a max length) or reactive variables.
|
|
432
|
+
): SyncValidator<T, K, V, R, A> // Explicitly type the validator you'll be returning
|
|
413
433
|
{
|
|
414
434
|
// Return a validator function
|
|
415
435
|
return (
|
|
416
436
|
// Strongly type the expected params object to have intellisense
|
|
417
|
-
params: ValidatorParams<T,
|
|
437
|
+
params: ValidatorParams<T, K, V, A>
|
|
418
438
|
) => {
|
|
419
439
|
// you can do whatever you want a normal validator can in here.
|
|
420
440
|
// Return undefined, an array of validators, or a validation result.
|
|
@@ -443,4 +463,47 @@ export function isEmailSync<T extends string | undefined | null>(): SyncValidato
|
|
|
443
463
|
|
|
444
464
|
This validator is effectively: `SyncValidator<string | undefined | null, unknown, unknown, unknown, unknown>`
|
|
445
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
|
+
|
|
476
|
+
## Technical Details
|
|
477
|
+
For those interested in the inner workings of the library without looking at the code:
|
|
478
|
+
|
|
479
|
+
- Reactive validation is performed by a deep watcher on the provided model. This was done because of inter-property dependence. When a validator for one property relies on another property in the object, it needs to be reevaluated. This does come with the technical debt of running *every* reactive validator *every* time the model is changed. However, the problem is mediated by validator optimizations which is discussed later.
|
|
480
|
+
|
|
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).
|
|
482
|
+
|
|
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.**
|
|
498
|
+
|
|
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.
|
|
500
|
+
|
|
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.
|
|
502
|
+
|
|
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.
|
|
504
|
+
|
|
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.
|
|
506
|
+
|
|
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).
|
|
508
|
+
|
|
446
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",
|