check-rule-mate 0.5.1 → 0.5.3

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)
@@ -31,6 +32,7 @@ Use **check-rule-mate** if you:
31
32
  - Need async validations (API calls, database checks, etc.)
32
33
  - Prefer rule-driven validation instead of tightly coupled schemas
33
34
  - Want clean separation between rules, data, and messages
35
+ - You want to have auto documentation of your forms
34
36
 
35
37
 
36
38
  ## Core Concepts
@@ -55,6 +57,7 @@ This separation makes the system flexible, scalable, and easy to maintain.
55
57
  - Strict or loose schema matching
56
58
  - i18n-ready error messages
57
59
  - Framework-agnostic (frontend or backend)
60
+ - Auto documentation (automatic generated by a CLI command)
58
61
 
59
62
  ## Table of Contents
60
63
 
@@ -64,6 +67,8 @@ This separation makes the system flexible, scalable, and easy to maintain.
64
67
  - [Running Tests](#Repository---Running-Tests)
65
68
  - [How It Works](#How-It-Works)
66
69
  - [Basic Usage](#Basic-Usage)
70
+ - [Lifecycle Hooks](#Lifecycle-Hooks)
71
+ - [Auto documentation](#Auto-Documentation)
67
72
  - [Defining Validation](#Defining-Validation)
68
73
  - [1. Schema](#Defining-a-Schema-What-to-validate)
69
74
  - [2. Rules](#Defining-Rules-How-to-validate)
@@ -164,6 +169,132 @@ When is **invalid** and **has errors**:
164
169
  }
165
170
  ```
166
171
 
172
+ ### Lifecycle Hooks
173
+ The **check-rule-mate** lifecycle hooks allow you to observe, extend and react to the validation process without coupling logic to rules or helpers.
174
+
175
+ Hooks are especially useful for:
176
+
177
+ - Logging and debugging
178
+ - Analytics and metrics
179
+ - Custom side-effects
180
+ - Integrating validation with UI or external systems
181
+ - Advanced error handling and reporting
182
+ - They provide fine-grained control over the validation lifecycle.
183
+
184
+ ```typescript
185
+ hooks?: {
186
+ onValidateStart?: (payload) => void;
187
+ onValidateFieldStart?: (payload) => void;
188
+ onValidateFieldError?: (payload) => void;
189
+ onValidateFieldSuccess?: (payload) => void;
190
+ onValidateEnd?: (payload) => void;
191
+ }
192
+ ```
193
+
194
+ #### Hook Payloads
195
+
196
+ ##### `onValidateStart`
197
+ Triggered once before validation begins.
198
+ ```typescript
199
+ onValidateStart: ({ data }) => void
200
+ ```
201
+
202
+ **Payload:**
203
+ - `data`: Full form data object being validated
204
+
205
+ ##### `onValidateFieldStart`
206
+ Triggered before validating each field.
207
+ ```typescript
208
+ onValidateFieldStart: ({ field, value, schemaField }) => void
209
+ ```
210
+
211
+ **Payload:**
212
+ - `field`: Field name
213
+ - `value`: Field value
214
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
215
+
216
+ ##### `onValidateFieldError`
217
+ Triggered when a field fails validation.
218
+ ```typescript
219
+ onValidateFieldError: ({ field, value, schemaField, error }) => void
220
+ ```
221
+
222
+ **Payload:**
223
+ - `field`: Field name
224
+ - `value`: Field value
225
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
226
+ - `error`: Validation error object
227
+
228
+ ##### `onValidateFieldSuccess`
229
+ Triggered when a field is successfully validated.
230
+ ```typescript
231
+ onValidateFieldSuccess: ({ field, value, schemaField }) => void
232
+ ```
233
+
234
+ **Payload:**
235
+ - `field`: Field name
236
+ - `value`: Field value
237
+ - `schemaField`: Schema configuration for the field (or `null` if missing)
238
+
239
+ ##### `onValidateEnd`
240
+ Triggered once after validation finishes.
241
+ ```typescript
242
+ onValidateEnd: ({ data, errors }) => void
243
+ ```
244
+
245
+ **Payload:**
246
+ - `data`: Full form data object being validated
247
+ - `errors`: Validation errors (if any)
248
+ If no errors occurred, `errors` may be `undefined`.
249
+
250
+ #### Hooks - Example Usage
251
+ ```javascript
252
+ const validator = createValidator(fields, {
253
+ validationHelpers: myValidator,
254
+ rules: MY_RULES,
255
+ schema: CONTACT_US,
256
+ errorMessages: ERROR_MESSAGES,
257
+ hooks: {
258
+ onValidateStart: ({ data }) => {
259
+ console.log('Validation started', data);
260
+ },
261
+
262
+ onValidateFieldStart: ({ field, value }) => {
263
+ console.log(`Validating ${field}`, value);
264
+ },
265
+
266
+ onValidateFieldError: ({ field, error }) => {
267
+ console.error(`Error on ${field}`, error);
268
+ },
269
+
270
+ onValidateFieldSuccess: ({ field }) => {
271
+ console.log(`${field} validated successfully`);
272
+ },
273
+
274
+ onValidateEnd: ({ data, errors }) => {
275
+ if (errors) {
276
+ console.log('Validation finished with errors', errors);
277
+ } else {
278
+ console.log('Validation finished successfully');
279
+ }
280
+ }
281
+ }
282
+ });
283
+
284
+ await validator.validate();
285
+ ```
286
+
287
+ ### Auto Documentation
288
+
289
+ **check-rule-mate** contains a script to generate **automatic documentation** based in your rules, schemas and error messages.
290
+
291
+ To use that it is simple, you only need to run this command:
292
+
293
+ ```bash
294
+ npx check-rule-mate-auto-docs --rules rules-path --schemas schemas-path --errors errors-path --out file.html
295
+ ```
296
+
297
+ This will generate a HTML file containing the rules, schemas and errors.
167
298
 
168
299
  ## Defining Validation
169
300
 
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-rule-mate — Auto Documentation Generator
4
+ */
5
+
6
+ // import fs from "fs";
7
+ // import path from "path";
8
+ // import process from "process";
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const process = require('process');
13
+
14
+ /* ---------------------------------------
15
+ * CLI ARGS
16
+ * -------------------------------------*/
17
+ const args = process.argv.slice(2);
18
+ const getArg = (flag) => {
19
+ const index = args.indexOf(flag);
20
+ return index !== -1 ? args[index + 1] : null;
21
+ };
22
+
23
+ const RULES_DIR = getArg('--rules');
24
+ const SCHEMAS_DIR = getArg('--schemas');
25
+ const ERRORS_DIR = getArg('--errors')
26
+ const OUTPUT = getArg('--out') || "check-rule-mate-docs.html";
27
+
28
+ if (!RULES_DIR || !SCHEMAS_DIR) {
29
+ console.error(`
30
+ Usage:
31
+ npx check-rule-mate-auto-docs --rules ./rules --schemas ./schemas --errors ./errors --out docs.html
32
+ `);
33
+ process.exit(1);
34
+ }
35
+
36
+ /* ---------------------------------------
37
+ * HELPERS
38
+ * -------------------------------------*/
39
+ const readJSONFiles = (dir) => {
40
+ return fs.readdirSync(dir)
41
+ .filter(f => f.endsWith(".json"))
42
+ .map(file => ({
43
+ name: file.replace(".json", ""),
44
+ content: JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"))
45
+ }));
46
+ };
47
+
48
+ const rulesFiles = readJSONFiles(RULES_DIR);
49
+ const schemasFiles = readJSONFiles(SCHEMAS_DIR);
50
+ const errorsFiles = readJSONFiles(ERRORS_DIR);
51
+
52
+
53
+ /* ---------------------------------------
54
+ * INDEX RELATIONSHIPS
55
+ * -------------------------------------*/
56
+ const ruleUsageMap = {};
57
+ const errorUsageMap = {}
58
+
59
+ schemasFiles.forEach(schemaFile => {
60
+ Object.entries(schemaFile.content).forEach(([field, config]) => {
61
+ const ruleName = config.rule.split("--")[0];
62
+ if (!ruleUsageMap[ruleName]) ruleUsageMap[ruleName] = [];
63
+ ruleUsageMap[ruleName].push({
64
+ schema: schemaFile.name,
65
+ field,
66
+ rule: config.rule
67
+ });
68
+ });
69
+ });
70
+
71
+ rulesFiles.forEach(rulesFile => {
72
+ Object.entries(rulesFile.content).forEach(([field, config]) => {
73
+ Object.entries(config.error).forEach(([key, value]) => {
74
+ const errorName = value;
75
+ if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
76
+ errorUsageMap[errorName].push({
77
+ file: rulesFile.name,
78
+ field,
79
+ error: value
80
+ });
81
+ });
82
+ if (config?.modifier) {
83
+ Object.entries(config.modifier).forEach(([modifierKey, modifier]) => {
84
+ if (modifier?.error) {
85
+ Object.entries(modifier.error).forEach(([key, value]) => {
86
+ const errorName = value;
87
+ if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
88
+ errorUsageMap[errorName].push({
89
+ file: rulesFile.name,
90
+ field,
91
+ error: value
92
+ });
93
+ });
94
+ }
95
+ });
96
+ }
97
+ });
98
+ });
99
+
100
+
101
+ /* ---------------------------------------
102
+ * HTML GENERATION
103
+ * -------------------------------------*/
104
+ const html = `
105
+ <!DOCTYPE html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="UTF-8" />
109
+ <title>check-rule-mate — Documentation</title>
110
+ <style>
111
+ ${generateCSS()}
112
+ </style>
113
+ </head>
114
+ <body>
115
+
116
+ <aside class="sidebar">
117
+ <input id="search" placeholder="Search..." />
118
+
119
+ <h3>Schemas</h3>
120
+ ${schemasFiles.map(s => `
121
+ <a href="#schema-${s.name}">${s.name}</a>
122
+ `).join("")}
123
+
124
+ <h3>Rules</h3>
125
+ ${rulesFiles.map(rf =>
126
+ Object.keys(rf.content).map(rule => `
127
+ <a href="#rule-${rule}">${rf.name} - ${rule}</a>
128
+ `).join("")
129
+ ).join("")}
130
+
131
+ <h3>Errors</h3>
132
+ ${errorsFiles.map(errorFile =>
133
+ Object.keys(errorFile.content).map(error => `
134
+ <a href="#error-${error}">${errorFile.name} - ${error}</a>
135
+ `).join("")
136
+ ).join("")}
137
+ </aside>
138
+
139
+ <main>
140
+ <section>
141
+ <h1>check-rule-mate</h1>
142
+ <p class="muted">
143
+ Visual documentation of validation rules and schemas.
144
+ </p>
145
+ </section>
146
+
147
+ ${renderSchemas(schemasFiles)}
148
+ ${renderRules(rulesFiles, ruleUsageMap)}
149
+ ${renderErrors(errorsFiles, errorUsageMap)}
150
+ </main>
151
+
152
+ <script>
153
+ ${generateClientJS()}
154
+ </script>
155
+
156
+ </body>
157
+ </html>
158
+ `;
159
+
160
+ fs.writeFileSync(OUTPUT, html);
161
+ console.log(`✔ Documentation generated at ${OUTPUT}`);
162
+
163
+ /* ---------------------------------------
164
+ * RENDERERS
165
+ * -------------------------------------*/
166
+ function renderSchemas(schemas) {
167
+ return schemas.map(schema => `
168
+ <section id="schema-${schema.name}" class="card">
169
+ <h2>Schema: ${schema.name}</h2>
170
+
171
+ <table>
172
+ <thead>
173
+ <tr>
174
+ <th>Field</th>
175
+ <th>Rule</th>
176
+ <th class="text-center">Required</th>
177
+ <th class="text-center">Cache</th>
178
+ </tr>
179
+ </thead>
180
+ <tbody>
181
+ ${Object.entries(schema.content).map(([field, cfg]) => `
182
+ <tr>
183
+ <td>${field}</td>
184
+ <td>
185
+ <a href="#rule-${cfg.rule.split("--")[0]}">
186
+ ${cfg.rule}
187
+ </a>
188
+ </td>
189
+ <td class="text-center">${cfg.required ? "✔" : "optional"}</td>
190
+ <td class="text-center">${cfg.cache === false ? "off" : "✔"}</td>
191
+ </tr>
192
+ `).join("")}
193
+ </tbody>
194
+ </table>
195
+ </section>
196
+ `).join("");
197
+ }
198
+
199
+ function renderRules(rulesFiles, usageMap) {
200
+ return rulesFiles.map(file =>
201
+ Object.entries(file.content).map(([ruleName, rule]) => `
202
+ <section id="rule-${ruleName}" class="card">
203
+ <h2>Rule: ${ruleName} (${file.name})</h2>
204
+ ${rule?.docs?.description ?
205
+ `<p>${rule.docs.description}</p>`
206
+ : ''}
207
+
208
+ <h3>Validation Flow</h3>
209
+ <div class="flow">
210
+ ${rule.validate.map(v => `
211
+ <div class="flow-step">${v}</div>
212
+ <div class="flow-arrow">→</div>
213
+ `).join("")}
214
+ <div class="flow-step success">valid</div>
215
+ </div>
216
+
217
+ <h3>Error Codes</h3>
218
+ ${renderRulesErrors(rule.error)}
219
+
220
+ ${rule.modifier ? `
221
+ <h3>Modifiers</h3>
222
+ ${Object.entries(rule.modifier).map(([mod, modRule]) => `
223
+ <div class="modifier">
224
+ <span class="tag modifier">${mod}</span>
225
+ ${modRule?.docs?.description ? `<p>${modRule.docs.description}</p>` : ''}
226
+
227
+ <div class="flow">
228
+ ${modRule.validate.map(v => `
229
+ <div class="flow-step">${v}</div>
230
+ <div class="flow-arrow">→</div>
231
+ `).join("")}
232
+ <div class="flow-step success">valid</div>
233
+ </div>
234
+
235
+ <h4>Error Codes</h4>
236
+ ${renderRulesErrors(modRule.error)}
237
+ </div>
238
+ `).join("")}
239
+ ` : ""}
240
+
241
+ <h3>Used by Schemas</h3>
242
+ <ul>
243
+ ${(usageMap[ruleName] || []).map(u =>
244
+ `<li>${u.schema} → <strong>${u.field}</strong> (${u.rule})</li>`
245
+ ).join("") || "<li>Not used</li>"}
246
+ </ul>
247
+
248
+ ${rule?.docs?.notes ?
249
+ `
250
+ <h3>Notes</h3>
251
+ <div class="rule-notes">${rule?.docs?.notes}</div>
252
+ `
253
+ : ''}
254
+ </section>
255
+ `).join("")
256
+ ).join("");
257
+ }
258
+
259
+ function renderRulesErrors(errors = {}) {
260
+ return `
261
+ <ul>
262
+ ${Object.entries(errors).map(([k, v]) =>
263
+ `<li><a href="#error-${v.split('.')[0]}" class="tag error">${k}</a> ${v}</li>`
264
+ ).join("")}
265
+ </ul>
266
+ `;
267
+ }
268
+
269
+ function renderErrors(errorFiles, usageMap) {
270
+ return errorFiles.map(file =>
271
+ Object.entries(file.content).map(([errorName, errors]) => `
272
+ <section id="error-${errorName}" class="card">
273
+ <h2>Error: ${errorName} (${file.name})</h2>
274
+ <ul>
275
+ ${Object.entries(errors).map(([key, value]) => `
276
+ <li><span class="tag error">${key}</span> ${value} </li>
277
+ `).join('')}
278
+ </ul>
279
+
280
+ <h3>Used by Rules</h3>
281
+ <ul>
282
+ ${Object.entries(errors).map(([key, value]) => `
283
+ ${(usageMap[`${errorName}.${key}`] || []).map(u =>
284
+ `<li>${u.file} → <strong>${u.field}</strong> (${u.error})</li>`
285
+ ).join("") || "<li>Not used</li>"}
286
+ `).join('')}
287
+ </ul>
288
+ </section>
289
+ `).join('')).join('');
290
+ }
291
+
292
+ /* ---------------------------------------
293
+ * CSS
294
+ * -------------------------------------*/
295
+ function generateCSS() {
296
+ return `
297
+ body {
298
+ margin: 0;
299
+ font-family: Inter, system-ui, sans-serif;
300
+ display: grid;
301
+ grid-template-columns: 280px 1fr;
302
+ background: #0f172a;
303
+ color: #e5e7eb;
304
+ }
305
+
306
+ .sidebar {
307
+ padding: 16px;
308
+ background: #020617;
309
+ overflow-y: auto;
310
+ }
311
+
312
+ .sidebar input {
313
+ width: 100%;
314
+ padding: 8px;
315
+ border-radius: 6px;
316
+ border: none;
317
+ margin-bottom: 16px;
318
+ }
319
+
320
+ .sidebar a {
321
+ display: block;
322
+ padding: 6px 10px;
323
+ color: #e5e7eb;
324
+ text-decoration: none;
325
+ border-radius: 6px;
326
+ }
327
+
328
+ .sidebar a:hover {
329
+ background: rgba(56,189,248,0.2);
330
+ }
331
+
332
+ a {
333
+ color: #e5e7eb;
334
+ }
335
+
336
+ main {
337
+ padding: 32px;
338
+ overflow-y: auto;
339
+ }
340
+
341
+ .card {
342
+ background: #020617;
343
+ border-radius: 12px;
344
+ padding: 24px;
345
+ margin-bottom: 32px;
346
+ border: 1px solid rgba(255,255,255,0.05);
347
+ }
348
+
349
+ .flow {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 12px;
353
+ flex-wrap: wrap;
354
+ }
355
+
356
+ .flow-step {
357
+ padding: 10px 14px;
358
+ border-radius: 8px;
359
+ background: rgba(56,189,248,0.15);
360
+ }
361
+
362
+ .flow-arrow {
363
+ opacity: 0.5;
364
+ }
365
+
366
+ .success {
367
+ background: rgba(34,197,94,0.2);
368
+ }
369
+
370
+ .tag {
371
+ padding: 4px 8px;
372
+ border-radius: 999px;
373
+ font-size: 14px;
374
+ }
375
+
376
+ .tag.error {
377
+ display: inline-block;
378
+ margin-bottom: 8px;
379
+ background: rgba(239,68,68,0.2);
380
+ text-decoration: none;
381
+ }
382
+
383
+ .tag.modifier {
384
+ display: inline-block;
385
+ font-size: 16px;
386
+ margin-bottom: 8px;
387
+ font-weight: 600;
388
+ background: rgba(167,139,250,0.2);
389
+ }
390
+
391
+ table {
392
+ width: 100%;
393
+ border-collapse: collapse;
394
+ }
395
+
396
+ td, th {
397
+ padding: 8px;
398
+ border-bottom: 1px solid rgba(255,255,255,0.05);
399
+ }
400
+
401
+ th {
402
+ text-align: left;
403
+ border-bottom: 1px solid #f4f4f4;
404
+ }
405
+
406
+ .rule-notes {
407
+ padding: 16px;
408
+ color: black;
409
+ background: #f4f4f4;
410
+ border-radius: 12px;
411
+ }
412
+
413
+ .text-center {
414
+ text-align: center;
415
+ }
416
+ `;
417
+ }
418
+
419
+ /* ---------------------------------------
420
+ * CLIENT JS
421
+ * -------------------------------------*/
422
+ function generateClientJS() {
423
+ return `
424
+ const search = document.getElementById("search");
425
+ search.addEventListener("input", e => {
426
+ const value = e.target.value.toLowerCase();
427
+ document.querySelectorAll("section.card").forEach(card => {
428
+ card.style.display = card.innerText.toLowerCase().includes(value)
429
+ ? ""
430
+ : "none";
431
+ });
432
+ });
433
+ `;
434
+ }
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.1",
3
+ "version": "0.5.3",
4
4
  "description": "",
5
5
  "main": "./dist/main.cjs.js",
6
6
  "type": "commonjs",
@@ -17,6 +17,9 @@
17
17
  "check rule",
18
18
  "check rule mate"
19
19
  ],
20
+ "bin": {
21
+ "check-rule-mate-auto-docs": "./bin/generate-docs.js"
22
+ },
20
23
  "scripts": {
21
24
  "start": "node ./examples/vanilla/src/index.js",
22
25
  "build": "node build",
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
  */