check-rule-mate 0.5.2 → 0.5.4

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 CHANGED
@@ -13,6 +13,7 @@ It is designed for scenarios where traditional schema-based validators start to
13
13
  - Async checks
14
14
  - Reusable validation logic across multiple forms
15
15
  - Full control over execution flow and error handling
16
+ - Lifecycle Hooks
16
17
 
17
18
 
18
19
  **Github repository:** [check-rule-mate repository](https://github.com/johnrock16/check-rule-mate)
@@ -57,6 +58,7 @@ This separation makes the system flexible, scalable, and easy to maintain.
57
58
  - i18n-ready error messages
58
59
  - Framework-agnostic (frontend or backend)
59
60
  - Auto documentation (automatic generated by a CLI command)
61
+ - Template checker (automatic checked by a CLI command)
60
62
 
61
63
  ## Table of Contents
62
64
 
@@ -66,7 +68,9 @@ This separation makes the system flexible, scalable, and easy to maintain.
66
68
  - [Running Tests](#Repository---Running-Tests)
67
69
  - [How It Works](#How-It-Works)
68
70
  - [Basic Usage](#Basic-Usage)
71
+ - [Lifecycle Hooks](#Lifecycle-Hooks)
69
72
  - [Auto documentation](#Auto-Documentation)
73
+ - [Auto Checker for Templates](#Auto-Checker-for-Templates)
70
74
  - [Defining Validation](#Defining-Validation)
71
75
  - [1. Schema](#Defining-a-Schema-What-to-validate)
72
76
  - [2. Rules](#Defining-Rules-How-to-validate)
@@ -167,18 +171,163 @@ When is **invalid** and **has errors**:
167
171
  }
168
172
  ```
169
173
 
174
+ ### Lifecycle Hooks
175
+ The **check-rule-mate** lifecycle hooks allow you to observe, extend and react to the validation process without coupling logic to rules or helpers.
176
+
177
+ Hooks are especially useful for:
178
+
179
+ - Logging and debugging
180
+ - Analytics and metrics
181
+ - Custom side-effects
182
+ - Integrating validation with UI or external systems
183
+ - Advanced error handling and reporting
184
+ - They provide fine-grained control over the validation lifecycle.
185
+
186
+ ```typescript
187
+ hooks?: {
188
+ onValidateStart?: (payload) => void;
189
+ onValidateFieldStart?: (payload) => void;
190
+ onValidateFieldError?: (payload) => void;
191
+ onValidateFieldSuccess?: (payload) => void;
192
+ onValidateEnd?: (payload) => void;
193
+ }
194
+ ```
195
+
196
+ #### Hook Payloads
197
+
198
+ ##### `onValidateStart`
199
+ Triggered once before validation begins.
200
+ ```typescript
201
+ onValidateStart: ({ data }) => void
202
+ ```
203
+
204
+ **Payload:**
205
+ - `data`: Full form data object being validated
206
+
207
+ ##### `onValidateFieldStart`
208
+ Triggered before validating each field.
209
+ ```typescript
210
+ onValidateFieldStart: ({ field, value, schemaField }) => void
211
+ ```
212
+
213
+ **Payload:**
214
+ - `field`: Field name
215
+ - `value`: Field value
216
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
217
+
218
+ ##### `onValidateFieldError`
219
+ Triggered when a field fails validation.
220
+ ```typescript
221
+ onValidateFieldError: ({ field, value, schemaField, error }) => void
222
+ ```
223
+
224
+ **Payload:**
225
+ - `field`: Field name
226
+ - `value`: Field value
227
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
228
+ - `error`: Validation error object
229
+
230
+ ##### `onValidateFieldSuccess`
231
+ Triggered when a field is successfully validated.
232
+ ```typescript
233
+ onValidateFieldSuccess: ({ field, value, schemaField }) => void
234
+ ```
235
+
236
+ **Payload:**
237
+ - `field`: Field name
238
+ - `value`: Field value
239
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
240
+
241
+ ##### `onValidateEnd`
242
+ Triggered once after validation finishes.
243
+ ```typescript
244
+ onValidateEnd: ({ data, errors }) => void
245
+ ```
246
+
247
+ **Payload:**
248
+ - `data`: Full form data object being validated
249
+ - `errors`: Validation errors (if any)
250
+ If no errors occurred, `errors` may be `undefined`.
251
+
252
+ #### Hooks - Example Usage
253
+ ```javascript
254
+ const validator = createValidator(fields, {
255
+ validationHelpers: myValidator,
256
+ rules: MY_RULES,
257
+ schema: CONTACT_US,
258
+ errorMessages: ERROR_MESSAGES,
259
+ hooks: {
260
+ onValidateStart: ({ data }) => {
261
+ console.log('Validation started', data);
262
+ },
263
+
264
+ onValidateFieldStart: ({ field, value }) => {
265
+ console.log(`Validating ${field}`, value);
266
+ },
267
+
268
+ onValidateFieldError: ({ field, error }) => {
269
+ console.error(`Error on ${field}`, error);
270
+ },
271
+
272
+ onValidateFieldSuccess: ({ field }) => {
273
+ console.log(`${field} validated successfully`);
274
+ },
275
+
276
+ onValidateEnd: ({ data, errors }) => {
277
+ if (errors) {
278
+ console.log('Validation finished with errors', errors);
279
+ } else {
280
+ console.log('Validation finished successfully');
281
+ }
282
+ }
283
+ }
284
+ });
285
+
286
+ await validator.validate();
287
+ ```
288
+
170
289
  ### Auto Documentation
171
290
 
172
- check-rule-mate contains a script to generate documentation based in your rules, schemas and error messages.
291
+ **check-rule-mate** contains a script to generate **automatic documentation** based in your rules, schemas and error messages.
173
292
 
174
293
  To use that it is simple, you only need to run this command:
175
294
 
176
295
  ```bash
177
- npx generate-docs --rules rules-path --schemas schemas-path --errors errors-path --out file.html
296
+ npx check-rule-mate-auto-docs --rules {rules path} --schemas {schemas path} --errors {errors path} --out file.html
178
297
  ```
179
298
 
180
299
  This will generate a HTML file containing the rules, schemas and errors.
181
300
 
301
+ ### Auto Checker for Templates
302
+ You can **auto check** if your template it is working properly with all necessary properties or have something missing.
303
+
304
+ It is works with: `Schemas`, `Rules` and `Errors`
305
+
306
+ ```bash
307
+ npx check-rule-mate-verify-templates --rules {rules path} --schemas {schemas path} --errors {errors path}
308
+ ```
309
+
310
+ If everything it is **working properly** this should be your message:
311
+ ```bash
312
+ ✔ Schemas loaded: 4
313
+ ✔ Rules loaded: 6
314
+ ✔ Errors loaded: 6
315
+
316
+ ✔ No issues found
317
+ ```
318
+
319
+ Else if some error was found could be something like that:
320
+ ```bash
321
+ ✔ Schemas loaded: 4
322
+ ✔ Rules loaded: 6
323
+ ✔ Errors loaded: 6
324
+
325
+ ❌ Validation failed
326
+
327
+ ❌ Schema "contactUs.json" → field "phone" references rule "cellphone" which does not exist
328
+ ```
329
+
330
+
182
331
  ## Defining Validation
183
332
 
184
333
  ### Defining a Schema (What to validate)
@@ -9,7 +9,7 @@
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
- const process = require('process')
12
+ const process = require('process');
13
13
 
14
14
  /* ---------------------------------------
15
15
  * CLI ARGS
@@ -28,7 +28,7 @@ const OUTPUT = getArg('--out') || "check-rule-mate-docs.html";
28
28
  if (!RULES_DIR || !SCHEMAS_DIR) {
29
29
  console.error(`
30
30
  Usage:
31
- node generate-docs.js --rules ./rules --schemas ./schemas --out docs.html
31
+ npx check-rule-mate-auto-docs --rules ./rules --schemas ./schemas --errors ./errors --out docs.html
32
32
  `);
33
33
  process.exit(1);
34
34
  }
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ // import fs from 'fs';
4
+ // import path from 'path';
5
+ // import process from 'process';
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const process = require('process');
10
+
11
+ /**
12
+ * ---------------------------
13
+ * Utils
14
+ * ---------------------------
15
+ */
16
+
17
+ function readJSONFilesFromDir(dirPath) {
18
+ if (!fs.existsSync(dirPath)) {
19
+ throw new Error(`Directory not found: ${dirPath}`);
20
+ }
21
+
22
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
23
+
24
+ const data = {};
25
+ for (const file of files) {
26
+ const fullPath = path.join(dirPath, file);
27
+ const content = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
28
+ data[file] = content;
29
+ }
30
+
31
+ return data;
32
+ }
33
+
34
+ function parseRuleName(ruleName) {
35
+ const [base, modifier] = ruleName.split('--');
36
+ return { base, modifier };
37
+ }
38
+
39
+ function error(msg) {
40
+ return { type: 'error', message: msg };
41
+ }
42
+
43
+ function warning(msg) {
44
+ return { type: 'warning', message: msg };
45
+ }
46
+
47
+ /**
48
+ * ---------------------------
49
+ * Core Validators
50
+ * ---------------------------
51
+ */
52
+
53
+ function validateSchemas({ schemas, rules }) {
54
+ const issues = [];
55
+
56
+ for (const [schemaFile, schema] of Object.entries(schemas)) {
57
+ for (const [field, config] of Object.entries(schema)) {
58
+ if (!config.rule) continue;
59
+
60
+ const { base, modifier } = parseRuleName(config.rule);
61
+
62
+ if (!rules[base]) {
63
+ issues.push(
64
+ error(
65
+ `Schema "${schemaFile}" → field "${field}" references rule "${base}" which does not exist`
66
+ )
67
+ );
68
+ continue;
69
+ }
70
+
71
+ if (modifier) {
72
+ if (!rules[base].modifier || !rules[base].modifier[modifier]) {
73
+ issues.push(
74
+ error(
75
+ `Schema "${schemaFile}" → field "${field}" references modifier "${modifier}" on rule "${base}" which does not exist`
76
+ )
77
+ );
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ return issues;
84
+ }
85
+
86
+ function validateRuleErrors({ rules, errors }) {
87
+ const issues = [];
88
+
89
+ for (const [ruleName, rule] of Object.entries(rules)) {
90
+ const checkErrorMap = (errorMap, context) => {
91
+ if (!errorMap) return;
92
+
93
+ for (const errorKey of Object.values(errorMap)) {
94
+ const [namespace, key] = errorKey.split('.');
95
+ if (!errors[namespace] || !errors[namespace][key]) {
96
+ issues.push(
97
+ error(
98
+ `Rule "${ruleName}"${context} references error key "${errorKey}" which does not exist`
99
+ )
100
+ );
101
+ }
102
+ }
103
+ };
104
+
105
+ checkErrorMap(rule.error, '');
106
+
107
+ if (rule.modifier) {
108
+ for (const [modifierName, modifier] of Object.entries(rule.modifier)) {
109
+ checkErrorMap(modifier.error, ` (modifier "${modifierName}")`);
110
+ }
111
+ }
112
+ }
113
+
114
+ return issues;
115
+ }
116
+
117
+ function findUnusedErrors({ rules, errors }) {
118
+ const issues = [];
119
+ const usedErrors = new Set();
120
+
121
+ const collectErrors = errorMap => {
122
+ if (!errorMap) return;
123
+ Object.values(errorMap).forEach(e => usedErrors.add(e));
124
+ };
125
+
126
+ for (const rule of Object.values(rules)) {
127
+ collectErrors(rule.error);
128
+ if (rule.modifier) {
129
+ Object.values(rule.modifier).forEach(m => collectErrors(m.error));
130
+ }
131
+ }
132
+
133
+ for (const [namespace, keys] of Object.entries(errors)) {
134
+ for (const key of Object.keys(keys)) {
135
+ const fullKey = `${namespace}.${key}`;
136
+ if (!usedErrors.has(fullKey)) {
137
+ issues.push(
138
+ warning(`Unused error message detected: "${fullKey}"`)
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ return issues;
145
+ }
146
+
147
+ /**
148
+ * ---------------------------
149
+ * CLI Argument Parsing
150
+ * ---------------------------
151
+ */
152
+
153
+ function parseArgs() {
154
+ const args = process.argv.slice(2);
155
+ const options = {};
156
+
157
+ for (let i = 0; i < args.length; i++) {
158
+ if (args[i].startsWith('--')) {
159
+ const key = args[i].replace('--', '');
160
+ options[key] = args[i + 1];
161
+ i++;
162
+ }
163
+ }
164
+
165
+ return options;
166
+ }
167
+
168
+ /**
169
+ * ---------------------------
170
+ * Runner
171
+ * ---------------------------
172
+ */
173
+
174
+ async function runVerify() {
175
+ try {
176
+ const args = parseArgs();
177
+
178
+ if (!args.schemas || !args.rules || !args.errors) {
179
+ console.error(
180
+ 'Usage: check-rule-mate-verify-templates --schemas <path> --rules <path> --errors <path>'
181
+ );
182
+ process.exit(1);
183
+ }
184
+
185
+ const schemas = readJSONFilesFromDir(args.schemas);
186
+ const rulesRaw = readJSONFilesFromDir(args.rules);
187
+ const errorsRaw = readJSONFilesFromDir(args.errors);
188
+
189
+ // Merge all rule files into one object
190
+ const rules = Object.assign({}, ...Object.values(rulesRaw));
191
+ const errors = Object.assign({}, ...Object.values(errorsRaw));
192
+
193
+ let issues = [];
194
+
195
+ issues.push(...validateSchemas({ schemas, rules }));
196
+ issues.push(...validateRuleErrors({ rules, errors }));
197
+ issues.push(...findUnusedErrors({ rules, errors }));
198
+
199
+ const hasErrors = issues.some(i => i.type === 'error');
200
+
201
+ console.log('✔ Schemas loaded:', Object.keys(schemas).length);
202
+ console.log('✔ Rules loaded:', Object.keys(rules).length);
203
+ console.log('✔ Errors loaded:', Object.keys(errors).length);
204
+ console.log('');
205
+
206
+ if (issues.length === 0) {
207
+ console.log('✔ No issues found');
208
+ process.exit(0);
209
+ }
210
+
211
+ console.log(hasErrors ? '❌ Validation failed' : '⚠️ Validation warnings');
212
+ console.log('');
213
+
214
+ for (const issue of issues) {
215
+ const prefix = issue.type === 'error' ? '❌' : '⚠️';
216
+ console.log(`${prefix} ${issue.message}`);
217
+ }
218
+
219
+ process.exit(hasErrors ? 1 : 0);
220
+ } catch (err) {
221
+ console.error('Fatal error:', err.message);
222
+ process.exit(1);
223
+ }
224
+ }
225
+
226
+ runVerify();
package/dist/main.cjs.js CHANGED
@@ -1 +1 @@
1
- var V=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var U=(r,n)=>{for(var u in n)V(r,u,{get:n[u],enumerable:!0})},F=(r,n,u,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let y of D(n))!T.call(r,y)&&y!==u&&V(r,y,{get:()=>n[y],enumerable:!(i=j(n,y))||i.enumerable});return r};var L=r=>F(V({},"__esModule",{value:!0}),r);var x={};U(x,{createValidator:()=>E});module.exports=L(x);var m={propertiesMustMatch:!0,abortEarly:!1,cache:!0};function E(r,{validationHelpers:n={},rules:u,schema:i,errorMessages:y={},options:f=m}){f={...m,...f},r=r;let k={},a={},p={all:async e=>Promise.all([...e].map(async s=>await v(s,r))),first:async e=>{let s=[];for(let l of e){let c=await v(l,r);if(s.push(c),!c)return s}return s}};function g(e,s){if(!e||typeof s!="string")return;let l=s.split("."),c=e;for(let d of l){if(c[d]===void 0)return;c=c[d]}return c}async function v(e,s=null){var l,c;if(i[e.key]){let{rule:d,required:t}=i[e.key];if((((l=i[e.key])==null?void 0:l.cache)!==void 0?i[e.key].cache:f.cache)&&s[e.key]===((c=a[e.key])==null?void 0:c.value))return a[e.key].isValid;if((d&&t||!t&&e.value!="")&&d){let O=d.split("--")[0],q=d.split("--").length>1?d.split("--")[1]:"",P=_(e.value,u[O],q,n,s),{isValid:w,errorMessage:R,errorType:I}=await P.validate();return w||(k[e.key]={name:e.key,code:R,type:I,message:g(y,R)||""}),a[e.key]={isValid:w,value:s[e.key]},w}}else if(f.propertiesMustMatch)return k[e.key]={name:e.key,message:"Invalid property"},!1;return!0}async function M(e){return await v({key:e,value:r[e]},r)?{ok:!0}:{error:!0,errors:k[e]}}async function o(){k={};let e=Object.keys(r).map(s=>({key:s,value:r[s]}));if(e&&e.length>0){if(!Object.keys(i).every(t=>r.hasOwnProperty(t)))return{error:!0,errorMessage:"Missing properties"};if((f!=null&&f.abortEarly?await p.first(e):await p.all(e)).some(t=>!t))return{error:!0,errors:k};let l=Object.keys(i).map(t=>({key:t,required:i[t].required})),c=e.map(t=>t.key);if(!l.filter(t=>t.required).map(t=>t.key).every(t=>c.includes(t)))return{error:!0}}else if(!e||e.length===0)return{error:!0,errorMessage:"Missing fields for schema"};return{ok:!0}}function h(e){r=e}return{validate:o,validateField:M,setData:h}}function _(r,n,u=null,i=null,y=null){async function f(a){let p,g;return{isValid:!(await Promise.all(a.validate.map(async o=>{let h=!0;if(a.params&&a.params[o]&&a.params[o].length>0){let e=a.params[o].map(l=>typeof l=="string"&&l[0]==="$"?l.substring(1,l.length):l);h=await this[o](...e)}else h=await this[o]();return!h&&!p&&(a!=null&&a.error[o])&&(p=a.error[o],g=o),h}))).some(o=>!o),errorMessage:p,errorType:g}}async function k(){if(i&&typeof i=="function"){let p=i(r,n,u,y);Object.keys(p).forEach(g=>{this[g]=p[g]})}return u?await f.call(this,n.modifier[u]):await f.call(this,n)}return{validate:k}}0&&(module.exports={createValidator});
1
+ var M=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var L=(r,a)=>{for(var u in a)M(r,u,{get:a[u],enumerable:!0})},_=(r,a,u,l)=>{if(a&&typeof a=="object"||typeof a=="function")for(let d of D(a))!U.call(r,d)&&d!==u&&M(r,d,{get:()=>a[d],enumerable:!(l=j(a,d))||l.enumerable});return r};var x=r=>_(M({},"__esModule",{value:!0}),r);var K={};L(K,{createValidator:()=>q});module.exports=x(K);var O={propertiesMustMatch:!0,abortEarly:!1,cache:!0};function q(r,{validationHelpers:a={},rules:u,schema:l,errorMessages:d={},hooks:g={},options:p=O}){p={...O,...p},r=r;let i={},y={},v={all:async e=>Promise.all([...e].map(async n=>await m(n,r))),first:async e=>{let n=[];for(let V of e){let t=await m(V,r);if(n.push(t),!t)return n}return n}};function w(e,n){if(!e||typeof n!="string")return;let V=n.split("."),t=e;for(let f of V){if(t[f]===void 0)return;t=t[f]}return t}async function m(e,n=null){var V,t;if(o("onValidateFieldStart",{field:e.key,value:e.value,schemaField:l[e.key]||null}),l[e.key]){let{rule:f,required:s}=l[e.key];if((((V=l[e.key])==null?void 0:V.cache)!==void 0?l[e.key].cache:p.cache)&&n[e.key]===((t=y[e.key])==null?void 0:t.value))return y[e.key].isValid;if((f&&s||!s&&e.value!="")&&f){let P=f.split("--")[0],S=f.split("--").length>1?f.split("--")[1]:"",I=B(e.value,u[P],S,a,n),{isValid:E,errorMessage:F,errorType:T}=await I.validate();if(E)o("onValidateFieldSuccess",{field:e.key,value:e.value,schemaField:l[e.key]});else{let R={name:e.key,field:e.key,code:F,type:T,message:w(d,F)||""};i[e.key]=R,o("onValidateFieldError",{field:e.key,value:e.value,schemaField:l[e.key],error:R})}return y[e.key]={isValid:E,value:n[e.key]},E}}else if(p.propertiesMustMatch){let f={name:e.key,field:e.key,message:"Invalid property",internal:!0};return i[e.key]=f,o("onValidateFieldError",{field:e.key,value:e.value,error:f}),!1}return o("onValidateFieldSuccess",{field:e.key,value:e.value}),!0}async function c(e){return await m({key:e,value:r[e]},r)?{ok:!0}:{error:!0,errors:i[e]}}async function k(){o("onValidateStart",{data:r}),i={};let e=Object.keys(r).map(n=>({key:n,value:r[n]}));if(e&&e.length>0){if(!Object.keys(l).every(s=>r.hasOwnProperty(s)))return o("onValidateEnd",{data:r,errors:[{name:"internal: schema - missing properties",message:"Missing properties",internal:!0}]}),{error:!0,errorMessage:"Missing properties"};if((p!=null&&p.abortEarly?await v.first(e):await v.all(e)).some(s=>!s))return o("onValidateEnd",{data:r,errors:i}),{error:!0,errors:i};let V=Object.keys(l).map(s=>({key:s,required:l[s].required})),t=e.map(s=>s.key);if(!V.filter(s=>s.required).map(s=>s.key).every(s=>t.includes(s))){let s={error:!0};return o("onValidateEnd",{data:r,errors:[{name:"internal: fields - required",message:"",internal:!0}]}),s}}else if(!e||e.length===0){let n={error:!0,errorMessage:"Missing fields for schema"};return o("onValidateEnd",{data:r,errors:[{name:"internal: schema - missing fields",message:n.errorMessage,internal:!0}]}),n}return o("onValidateEnd",{data:r}),{ok:!0}}function o(e,n){g!=null&&g[e]&&typeof g[e]=="function"&&g[e]({...n})}function h(e){r=e}return{validate:k,validateField:c,setData:h}}function B(r,a,u=null,l=null,d=null){async function g(i){let y,v;return{isValid:!(await Promise.all(i.validate.map(async c=>{let k=!0;if(i.params&&i.params[c]&&i.params[c].length>0){let o=i.params[c].map(e=>typeof e=="string"&&e[0]==="$"?e.substring(1,e.length):e);k=await this[c](...o)}else k=await this[c]();return!k&&!y&&(i!=null&&i.error[c])&&(y=i.error[c],v=c),k}))).some(c=>!c),errorMessage:y,errorType:v}}async function p(){if(l&&typeof l=="function"){let y=l(r,a,u,d);Object.keys(y).forEach(v=>{this[v]=y[v]})}return u?await g.call(this,a.modifier[u]):await g.call(this,a)}return{validate:p}}0&&(module.exports={createValidator});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "check-rule-mate",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "",
5
5
  "main": "./dist/main.cjs.js",
6
6
  "type": "commonjs",
@@ -18,7 +18,8 @@
18
18
  "check rule mate"
19
19
  ],
20
20
  "bin": {
21
- "generate-docs": "./bin/generate-docs.js"
21
+ "check-rule-mate-auto-docs": "./bin/generate-docs.js",
22
+ "check-rule-mate-verify-templates": "./bin/verify-templates.js"
22
23
  },
23
24
  "scripts": {
24
25
  "start": "node ./examples/vanilla/src/index.js",
package/src/index.d.ts CHANGED
@@ -86,6 +86,9 @@ export interface DataValidatorConfigs {
86
86
 
87
87
  /** Validator options */
88
88
  options: ValidatorOptions
89
+
90
+ /** Validator hooks */
91
+ hooks: ValidatorHooks
89
92
  }
90
93
 
91
94
  /**
@@ -100,9 +103,12 @@ export interface DataValidatorSuccessResponse {
100
103
  * Error object.
101
104
  */
102
105
  export interface CheckError {
103
- /** Field name */
106
+ /** Error name */
104
107
  name: string
105
108
 
109
+ /** Field name */
110
+ field?: string
111
+
106
112
  /** Error path */
107
113
  code?: string
108
114
 
@@ -111,6 +117,9 @@ export interface CheckError {
111
117
 
112
118
  /** Error message */
113
119
  message?: string
120
+
121
+ /** Internal error flag */
122
+ internal?: boolean
114
123
  }
115
124
 
116
125
  /**
@@ -126,3 +135,94 @@ export interface DataValidatorErrorResponse {
126
135
  /** Additional error details */
127
136
  errors?: Record<string, CheckError>
128
137
  }
138
+
139
+ export interface ValidatorHooks {
140
+ /** Executed before validation runs */
141
+ onValidateStart?: onValidateStart
142
+
143
+ /** Executed before validation runs for each field */
144
+ onValidateFieldStart?: onValidateFieldStart
145
+
146
+ /** Executed when validation fails for some field */
147
+ onValidateFieldError?: onValidateFieldError
148
+
149
+ /** Executed when validation has success for some field */
150
+ onValidateFieldSuccess?: onValidateFieldSuccess
151
+
152
+ /** Executed after validation runs */
153
+ onValidateEnd?: onValidateEnd
154
+ }
155
+
156
+ export interface onValidateStart {
157
+ /** Returns the payload of hook */
158
+ payload: onValidateStartPayload
159
+ }
160
+
161
+ export interface onValidateFieldStart {
162
+ /** Returns the payload of hook */
163
+ payload: onValidateFieldStartPayload
164
+ }
165
+
166
+ export interface onValidateFieldError {
167
+ /** Returns the payload of hook */
168
+ payload: onValidateFieldErrorPayload
169
+ }
170
+
171
+ export interface onValidateFieldSuccess {
172
+ /** Returns the payload of hook */
173
+ payload: onValidateFieldSuccessPayload
174
+ }
175
+
176
+ export interface onValidateEnd {
177
+ /** Returns the payload of hook */
178
+ payload: onValidateEndPayload
179
+ }
180
+
181
+ export interface onValidateStartPayload {
182
+ /** Form data object */
183
+ data: Object
184
+ }
185
+
186
+ export interface onValidateFieldStart {
187
+ /** Field name */
188
+ field: string
189
+
190
+ /** Field value */
191
+ value: any
192
+
193
+ /** Schema field */
194
+ schemaField: SchemaRuleField
195
+ }
196
+
197
+ export interface onValidateFieldError {
198
+ /** Field name */
199
+ field: string
200
+
201
+ /** Field value */
202
+ value: any
203
+
204
+ /** Schema field */
205
+ schemaField: SchemaRuleField
206
+
207
+ /** Field error */
208
+ error: CheckError
209
+ }
210
+
211
+ export interface onValidateFieldSuccess {
212
+ /** Field name */
213
+ field: string
214
+
215
+ /** Field value */
216
+ value: any
217
+
218
+ /** Schema field */
219
+ schemaField: SchemaRuleField
220
+ }
221
+
222
+ export interface onValidateEndPayload {
223
+ /** Form data object */
224
+ data: Object
225
+
226
+ /** Form errors */
227
+ errors?: Record<string, CheckError>
228
+ }
@@ -5,7 +5,7 @@ const DEFAUL_OPTIONS = { propertiesMustMatch: true, abortEarly: false, cache: tr
5
5
  * @param {[DataField]} data - All the data fields to be validate
6
6
  * @param {DataValidatorConfigs} config - The configs which will be followed during validation
7
7
  */
8
- export function createValidator(data, {validationHelpers = {}, rules, schema, errorMessages = {}, options = DEFAUL_OPTIONS}) {
8
+ export function createValidator(data, {validationHelpers = {}, rules, schema, errorMessages = {}, hooks = {}, options = DEFAUL_OPTIONS}) {
9
9
  options = { ...DEFAUL_OPTIONS, ...options};
10
10
  data = data;
11
11
  let errors = {};
@@ -41,6 +41,7 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
41
41
  }
42
42
 
43
43
  async function inputValidation(dataAttribute, data = null) {
44
+ hookTrigger('onValidateFieldStart', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] || null });
44
45
  if (schema[dataAttribute.key]) {
45
46
  const { rule, required } = schema[dataAttribute.key];
46
47
  const cacheEnabled = schema[dataAttribute.key]?.cache !== undefined ? schema[dataAttribute.key].cache : options.cache;
@@ -56,24 +57,36 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
56
57
  const dataAttributeValidation = dataAttributeValidator(dataAttribute.value, rules[INPUT_RULE], RULE_MODIFIER, validationHelpers, data);
57
58
  const { isValid, errorMessage, errorType } = await dataAttributeValidation.validate();
58
59
  if (!isValid) {
59
- errors[dataAttribute.key] = {
60
+ const error = {
60
61
  name: dataAttribute.key,
62
+ field: dataAttribute.key,
61
63
  code: errorMessage,
62
64
  type: errorType,
63
65
  message: getObjectValueByPath(errorMessages, errorMessage) || ''
64
- }
66
+ };
67
+
68
+ errors[dataAttribute.key] = error;
69
+ hookTrigger('onValidateFieldError', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] , error: error });
70
+ } else {
71
+ hookTrigger('onValidateFieldSuccess', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] });
65
72
  }
66
73
  oldData[dataAttribute.key] = {isValid: isValid, value: data[dataAttribute.key]};
67
74
  return isValid;
68
75
  }
69
76
  }
70
77
  } else if (options.propertiesMustMatch) {
71
- errors[dataAttribute.key] = {
78
+ const error = {
72
79
  name: dataAttribute.key,
73
- message: "Invalid property"
80
+ field: dataAttribute.key,
81
+ message: "Invalid property",
82
+ internal: true
74
83
  }
84
+
85
+ errors[dataAttribute.key] = error;
86
+ hookTrigger('onValidateFieldError', { field: dataAttribute.key, value: dataAttribute.value, error: error });
75
87
  return false;
76
88
  }
89
+ hookTrigger('onValidateFieldSuccess', { field: dataAttribute.key, value: dataAttribute.value });
77
90
  return true;
78
91
  }
79
92
 
@@ -95,15 +108,18 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
95
108
  * @returns {DataValidatorSuccessResponse | DataValidatorErrorResponse} - The response of your validation
96
109
  */
97
110
  async function validate() {
111
+ hookTrigger('onValidateStart', { data });
98
112
  errors = {};
99
113
  let dataArr = Object.keys(data).map((key) => ({ key, value: data[key] }));
100
114
  if (dataArr && dataArr.length > 0) {
101
115
  if(!Object.keys(schema).every((key) => data.hasOwnProperty(key))) {
116
+ hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: schema - missing properties' , message: 'Missing properties', internal: true}] });
102
117
  return { error: true, errorMessage: "Missing properties"}
103
118
  }
104
119
  const dataValidators = options?.abortEarly ? await validateByStrategies.first(dataArr) : await validateByStrategies.all(dataArr);
105
120
 
106
121
  if (dataValidators.some((element) => !element)) {
122
+ hookTrigger('onValidateEnd', { data, errors: errors });
107
123
  return { error: true, errors: errors };
108
124
  }
109
125
 
@@ -112,14 +128,27 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
112
128
  const dataAttributesRequired = dataRuleArr.filter((rule) => rule.required).map((rule) => rule.key);
113
129
 
114
130
  if (!dataAttributesRequired.every((fieldRequired) => dataAttributesKey.includes(fieldRequired))) {
115
- return { error: true };
131
+ const error = { error: true };
132
+
133
+ hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: fields - required' , message: '', internal: true}] });
134
+ return error;
116
135
  }
117
136
  } else if (!dataArr || dataArr.length === 0) {
118
- return { error: true, errorMessage: "Missing fields for schema"}
137
+ const error = { error: true, errorMessage: "Missing fields for schema"};
138
+
139
+ hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: schema - missing fields' , message: error.errorMessage, internal: true}] });
140
+ return error;
119
141
  }
142
+ hookTrigger('onValidateEnd', { data });
120
143
  return { ok: true };
121
144
  }
122
145
 
146
+ function hookTrigger(hookName, parameters) {
147
+ if (hooks?.[hookName] && typeof hooks[hookName] === 'function') {
148
+ hooks[hookName]({...parameters});
149
+ }
150
+ }
151
+
123
152
  function setData(newData) {
124
153
  data = newData;
125
154
  }
package/src/types.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * @property {SchemaRule} schema - The rules you want to use per field
17
17
  * @property {Object} errorMessages - The error messages you want to show during errors
18
18
  * @property {ValidatorOptions} options - Options
19
+ * @property {ValidatorHooks} hooks - The hooks you want to execute in some specific phase of validation
19
20
  */
20
21
 
21
22
  /**
@@ -74,8 +75,78 @@
74
75
  * Error Object
75
76
  *
76
77
  * @typedef {Object} CheckError
77
- * @property {string} name - Field name
78
+ * @property {string} name - Error name
79
+ * @property {string} field - Field name (optional)
78
80
  * @property {string} code - Error path (optional)
79
81
  * @property {string} type - Error type (optional)
80
82
  * @property {string} message - Error message (optional)
83
+ * @property {boolean} internal - Flag to know if it is a internal error (optional)
84
+ */
85
+
86
+
87
+ /**
88
+ * @typedef {Object} ValidatorHooks
89
+ * @property {onValidateStart} onValidateStart - Executed before validation runs
90
+ * @property {onValidateFieldStart} onValidateFieldStart - Executed before validation runs for each field
91
+ * @property {onValidateFieldError} onValidateFieldError - Executed when validation fails for some field
92
+ * @property {onValidateFieldSuccess} onValidateFieldSuccess - Executed when validation has success for some field
93
+ * @property {onValidateEnd} onValidateEnd - Executed after validation runs
94
+ */
95
+
96
+ /**
97
+ * @typedef {Function} onValidateStart
98
+ * @property {onValidateStartPayload} payload - Returns the payload of validate form hook
99
+ */
100
+
101
+ /**
102
+ * @typedef {Object} onValidateStartPayload
103
+ * @property {Object} data - Returns the form data
104
+ */
105
+
106
+ /**
107
+ * @typedef {Function} onValidateFieldStart
108
+ * @property {onValidateFieldStartPayload} payload - Returns the payload of validate field hook
109
+ */
110
+
111
+ /**
112
+ * @typedef {Object} onValidateFieldStartPayload
113
+ * @property {string} field - Returns the field name
114
+ * @property {any} value - Returns the value of field
115
+ * @property {SchemaRuleField} schemaField - Returns the schema field
116
+ */
117
+
118
+ /**
119
+ * @typedef {Function} onValidateFieldError
120
+ * @property {onValidateFieldErrorPayload} payload - Returns the payload of validate field error hook
121
+ */
122
+
123
+ /**
124
+ * @typedef {Object} onValidateFieldErrorPayload
125
+ * @property {string} field - Returns the field name
126
+ * @property {string} value - Returns the value of field
127
+ * @property {SchemaRuleField} schemaField - Returns the schema field
128
+ * @property {CheckError} error - Returns the error data
129
+ */
130
+
131
+ /**
132
+ * @typedef {Function} onValidateFieldSuccess
133
+ * @property {onValidateFieldSuccessPayload} payload - Returns the payload of validate field success hook
134
+ */
135
+
136
+ /**
137
+ * @typedef {Object} onValidateFieldSuccessPayload
138
+ * @property {string} field - Returns the field name
139
+ * @property {string} value - Returns the value of field
140
+ * @property {SchemaRuleField} schemaField - Returns the schema field
141
+ */
142
+
143
+ /**
144
+ * @typedef {Function} onValidateEnd
145
+ * @property {onValidateEndPayload} payload - Returns the payload of validate form hook
146
+ */
147
+
148
+ /**
149
+ * @typedef {Object} onValidateEndPayload
150
+ * @property {Object} data - Returns the form data
151
+ * @property {DataValidatorErrorResponse} errors - Returns the errors of validation if had
81
152
  */