check-rule-mate 0.5.0 → 0.5.2

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
@@ -31,6 +31,7 @@ Use **check-rule-mate** if you:
31
31
  - Need async validations (API calls, database checks, etc.)
32
32
  - Prefer rule-driven validation instead of tightly coupled schemas
33
33
  - Want clean separation between rules, data, and messages
34
+ - You want to have auto documentation of your forms
34
35
 
35
36
 
36
37
  ## Core Concepts
@@ -55,20 +56,22 @@ This separation makes the system flexible, scalable, and easy to maintain.
55
56
  - Strict or loose schema matching
56
57
  - i18n-ready error messages
57
58
  - Framework-agnostic (frontend or backend)
59
+ - Auto documentation (automatic generated by a CLI command)
58
60
 
59
61
  ## Table of Contents
60
62
 
61
63
  - [Getting Started](#Getting-Started)
62
64
  - [NPM - Installation](#Installation)
63
65
  - [Repository - Installation](#Repository---Installation)
64
- - [Running Tests](#Running-Tests)
66
+ - [Running Tests](#Repository---Running-Tests)
65
67
  - [How It Works](#How-It-Works)
66
- - [Basic Example](#Basic-Example)
68
+ - [Basic Usage](#Basic-Usage)
69
+ - [Auto documentation](#Auto-Documentation)
67
70
  - [Defining Validation](#Defining-Validation)
68
71
  - [1. Schema](#Defining-a-Schema-What-to-validate)
69
72
  - [2. Rules](#Defining-Rules-How-to-validate)
70
73
  - [3. Validation Helpers](#Validation-Helpers-Execution-Layer)
71
- - [4. Error Messages](#Error-Messages)
74
+ - [4. Error Messages](#Error-Messages-i18n-ready)
72
75
  - [5. Example Usage](#Example-Usage)
73
76
  - [Vanilla](#vanilla)
74
77
  - [Express](#express)
@@ -126,8 +129,9 @@ async function runFormValidate() {
126
129
  schema: CONTACT_US,
127
130
  errorMessages: ERROR_MESSAGES,
128
131
  options: {
132
+ cache: true,
129
133
  abortEarly: false,
130
- propertiesMustMatch: true
134
+ propertiesMustMatch: true,
131
135
  }
132
136
  });
133
137
 
@@ -149,18 +153,31 @@ When is **valid**:
149
153
  ```
150
154
 
151
155
  When is **invalid** and **has errors**:
152
- ```javascript
156
+ ```typescript
153
157
  {
154
- error: true;
158
+ error: true,
155
159
  errors: {
156
160
  [field: string]: {
157
- type: string;
158
- message: string;
161
+ name: string,
162
+ type: string,
163
+ message: string,
164
+ code: string,
159
165
  }[];
160
166
  };
161
167
  }
162
168
  ```
163
169
 
170
+ ### Auto Documentation
171
+
172
+ check-rule-mate contains a script to generate documentation based in your rules, schemas and error messages.
173
+
174
+ To use that it is simple, you only need to run this command:
175
+
176
+ ```bash
177
+ npx generate-docs --rules rules-path --schemas schemas-path --errors errors-path --out file.html
178
+ ```
179
+
180
+ This will generate a HTML file containing the rules, schemas and errors.
164
181
 
165
182
  ## Defining Validation
166
183
 
@@ -175,11 +192,13 @@ Schemas map **data fields** to **rules**.
175
192
  },
176
193
  "email": {
177
194
  "rule": "email",
178
- "required": true
195
+ "required": true,
196
+ "cache": false,
179
197
  },
180
198
  "emailConfirm": {
181
199
  "rule": "email--confirm",
182
- "required": true
200
+ "required": true,
201
+ "cache": false,
183
202
  },
184
203
  "phone": {
185
204
  "rule": "phone",
@@ -191,6 +210,7 @@ Schemas map **data fields** to **rules**.
191
210
  #### Schema Properties
192
211
  - **rule**: Rule name (supports modifiers via `rule--modifier`)
193
212
  - **required**: Whether the field must exist and not be empty
213
+ - **cache**: if this field will have cache or not
194
214
 
195
215
  ### Defining Rules (How to validate)
196
216
 
@@ -350,8 +370,9 @@ This makes localization and message customization straightforward.
350
370
  ### Validation Options
351
371
  ```typescript
352
372
  options: {
353
- abortEarly?: boolean; // Stop on first error
354
- propertiesMustMatch?: boolean; // Schema vs data strictness
373
+ cache?: boolean, // If cache is enabled or not
374
+ abortEarly?: boolean, // Stop on first error
375
+ propertiesMustMatch?: boolean, // Schema vs data strictness
355
376
  }
356
377
  ```
357
378
 
@@ -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
+ node generate-docs.js --rules ./rules --schemas ./schemas --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 k=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var j=(u,n)=>{for(var c in n)k(u,c,{get:n[c],enumerable:!0})},I=(u,n,c,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let f of b(n))!P.call(u,f)&&f!==c&&k(u,f,{get:()=>n[f],enumerable:!(i=E(n,f))||i.enumerable});return u};var T=u=>I(k({},"__esModule",{value:!0}),u);var x={};j(x,{createValidator:()=>V});module.exports=T(x);function V(u,{validationHelpers:n={},rules:c,schema:i,errorMessages:f={},options:y={propertiesMustMatch:!0,abortEarly:!1}}){let p={},r=u,d={all:async e=>Promise.all([...e].map(async s=>await h(s,r))),first:async e=>{let s=[];for(let t of e){let l=await h(t,r);if(s.push(l),!l)return s}return s}};function g(e,s){if(!e||typeof s!="string")return;let t=s.split("."),l=e;for(let M of t){if(l[M]===void 0)return;l=l[M]}return l}async function h(e,s=null){if(i[e.key]){let{rule:t,required:l}=i[e.key];if((t&&l||!l&&e.value!="")&&t){let M=t.split("--")[0],a=t.split("--").length>1?t.split("--")[1]:"",O=U(e.value,c[M],a,n,s),{isValid:R,errorMessage:m,errorType:q}=await O.validate();return R||(p[e.key]={name:e.key,error:!0,errorMessage:g(f,m)||m,errorType:q}),R}}else if(y.propertiesMustMatch)return p[e.key]={name:e.key,error:!0,errorMessage:"Invalid property"},!1;return!0}async function w(e){return await h({key:e,value:r[e]},r)?{ok:!0}:{error:!0,errors:p[e]}}async function o(){p={};let e=Object.keys(r).map(s=>({key:s,value:r[s]}));if(e&&e.length>0){if(!Object.keys(i).every(a=>r.hasOwnProperty(a)))return{error:!0,errorMessage:"Missing properties"};if((y!=null&&y.abortEarly?await d.first(e):await d.all(e)).some(a=>!a))return{error:!0,errors:p};let t=Object.keys(i).map(a=>({key:a,required:i[a].required})),l=e.map(a=>a.key);if(!t.filter(a=>a.required).map(a=>a.key).every(a=>l.includes(a)))return{error:!0}}else if(!e||e.length===0)return{error:!0,errorMessage:"Missing fields for schema"};return{ok:!0}}function v(e){r=e}return{validate:o,validateField:w,setData:v}}function U(u,n,c=null,i=null,f=null){async function y(r){let d,g;return{isValid:!(await Promise.all(r.validate.map(async o=>{let v=!0;if(r.params&&r.params[o]&&r.params[o].length>0){let e=r.params[o].map(t=>typeof t=="string"&&t[0]==="$"?t.substring(1,t.length):t);v=await this[o](...e)}else v=await this[o]();return!v&&!d&&(r!=null&&r.error[o])&&(d=r.error[o],g=o),v}))).some(o=>!o),errorMessage:d,errorType:g}}async function p(){if(i&&typeof i=="function"){let d=i(u,n,c,f);Object.keys(d).forEach(g=>{this[g]=d[g]})}return c?await y.call(this,n.modifier[c]):await y.call(this,n)}return{validate:p}}0&&(module.exports={createValidator});
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});
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "check-rule-mate",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "",
5
5
  "main": "./dist/main.cjs.js",
6
6
  "type": "commonjs",
7
+ "types": "index.d.ts",
7
8
  "keywords": [
8
9
  "data validation",
9
10
  "data validation js",
@@ -16,6 +17,9 @@
16
17
  "check rule",
17
18
  "check rule mate"
18
19
  ],
20
+ "bin": {
21
+ "generate-docs": "./bin/generate-docs.js"
22
+ },
19
23
  "scripts": {
20
24
  "start": "node ./examples/vanilla/src/index.js",
21
25
  "build": "node build",
package/src/index.d.ts ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * This file contains all type definitions used in the project.
3
+ */
4
+
5
+ /**
6
+ * Represents a single data field.
7
+ */
8
+ export interface DataField {
9
+ /** The name and key of the field */
10
+ name: string
11
+
12
+ /** The value of the field */
13
+ value: string
14
+ }
15
+
16
+ /**
17
+ * Options to control validator behavior.
18
+ */
19
+ export interface ValidatorOptions {
20
+ /** If the form fields don't match the expected structure, triggers an error */
21
+ propertiesMustMatch: boolean
22
+
23
+ /** Stops validation when the first error is caught */
24
+ abortEarly: boolean
25
+
26
+ /** Defines if the schema will use cache by default */
27
+ cache: boolean
28
+ }
29
+
30
+ /**
31
+ * A single validation helper function.
32
+ */
33
+ export type ValidatorHelper = (
34
+ value: any,
35
+ rule: string,
36
+ modifier: string,
37
+ data: DataField[]
38
+ ) => boolean
39
+
40
+ /**
41
+ * A map of validation helper functions.
42
+ */
43
+ export type ValidationHelpers = Record<string, ValidatorHelper>
44
+
45
+ /**
46
+ * Schema rule for a single field.
47
+ */
48
+ export interface SchemaRuleField {
49
+ /** The validation rule for the field (e.g., "name", "email", "phone", "hasText") */
50
+ rule: string
51
+
52
+ /** Indicates whether the field is required */
53
+ required: boolean
54
+
55
+ /** Indicates if the field requires cache or not */
56
+ cache: boolean
57
+ }
58
+
59
+ /**
60
+ * Schema rule object.
61
+ */
62
+ export interface SchemaRule {
63
+ field: SchemaRuleField
64
+ }
65
+
66
+ /**
67
+ * Validation schema mapping field names to rules.
68
+ */
69
+ export type SchemaRules = Record<string, SchemaRule>
70
+
71
+ /**
72
+ * Validator configuration object.
73
+ */
74
+ export interface DataValidatorConfigs {
75
+ /** The validator functions to help your validations */
76
+ validationHelpers: ValidationHelpers
77
+
78
+ /** The rules you want to use through validation */
79
+ rules: object
80
+
81
+ /** The rules you want to use per field */
82
+ schema: SchemaRules
83
+
84
+ /** The error messages you want to show during errors */
85
+ errorMessages: object
86
+
87
+ /** Validator options */
88
+ options: ValidatorOptions
89
+ }
90
+
91
+ /**
92
+ * Represents a successful response.
93
+ */
94
+ export interface DataValidatorSuccessResponse {
95
+ /** Indicates the operation was successful */
96
+ ok: boolean
97
+ }
98
+
99
+ /**
100
+ * Error object.
101
+ */
102
+ export interface CheckError {
103
+ /** Field name */
104
+ name: string
105
+
106
+ /** Error path */
107
+ code?: string
108
+
109
+ /** Error type */
110
+ type?: string
111
+
112
+ /** Error message */
113
+ message?: string
114
+ }
115
+
116
+ /**
117
+ * Represents an error response.
118
+ */
119
+ export interface DataValidatorErrorResponse {
120
+ /** Indicates an error occurred */
121
+ error: boolean
122
+
123
+ /** A message describing the error */
124
+ errorMessage?: string
125
+
126
+ /** Additional error details */
127
+ errors?: Record<string, CheckError>
128
+ }
@@ -1,11 +1,15 @@
1
+ const DEFAUL_OPTIONS = { propertiesMustMatch: true, abortEarly: false, cache: true };
2
+
1
3
  /**
2
4
  * Validate your data fields using your rules, data rules and validators.
3
- * @param {[DataField]} dataParameter - All the data fields to be validate
5
+ * @param {[DataField]} data - All the data fields to be validate
4
6
  * @param {DataValidatorConfigs} config - The configs which will be followed during validation
5
7
  */
6
- export function createValidator(dataParameter, {validationHelpers = {}, rules, schema, errorMessages = {}, options = { propertiesMustMatch: true, abortEarly: false}}) {
8
+ export function createValidator(data, {validationHelpers = {}, rules, schema, errorMessages = {}, options = DEFAUL_OPTIONS}) {
9
+ options = { ...DEFAUL_OPTIONS, ...options};
10
+ data = data;
7
11
  let errors = {};
8
- let data = dataParameter;
12
+ let oldData = {};
9
13
 
10
14
  const validateByStrategies = {
11
15
  all: async (dataArr) => Promise.all([...dataArr].map(async (input) => await inputValidation(input, data))),
@@ -39,6 +43,12 @@ export function createValidator(dataParameter, {validationHelpers = {}, rules, s
39
43
  async function inputValidation(dataAttribute, data = null) {
40
44
  if (schema[dataAttribute.key]) {
41
45
  const { rule, required } = schema[dataAttribute.key];
46
+ const cacheEnabled = schema[dataAttribute.key]?.cache !== undefined ? schema[dataAttribute.key].cache : options.cache;
47
+
48
+ if (cacheEnabled && data[dataAttribute.key] === oldData[dataAttribute.key]?.value) {
49
+ return oldData[dataAttribute.key].isValid;
50
+ }
51
+
42
52
  if ((rule && required) || (!required && dataAttribute.value != '')) {
43
53
  if (rule) {
44
54
  const INPUT_RULE = rule.split('--')[0];
@@ -48,19 +58,19 @@ export function createValidator(dataParameter, {validationHelpers = {}, rules, s
48
58
  if (!isValid) {
49
59
  errors[dataAttribute.key] = {
50
60
  name: dataAttribute.key,
51
- error: true,
52
- errorMessage: getObjectValueByPath(errorMessages, errorMessage) || errorMessage,
53
- errorType: errorType
61
+ code: errorMessage,
62
+ type: errorType,
63
+ message: getObjectValueByPath(errorMessages, errorMessage) || ''
54
64
  }
55
65
  }
66
+ oldData[dataAttribute.key] = {isValid: isValid, value: data[dataAttribute.key]};
56
67
  return isValid;
57
68
  }
58
69
  }
59
70
  } else if (options.propertiesMustMatch) {
60
71
  errors[dataAttribute.key] = {
61
72
  name: dataAttribute.key,
62
- error: true,
63
- errorMessage: "Invalid property"
73
+ message: "Invalid property"
64
74
  }
65
75
  return false;
66
76
  }
package/src/types.js CHANGED
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * @typedef {Object} DataField
8
8
  * @property {string} name - The name and key of the field
9
- * @property {string} value - the value of the field
9
+ * @property {any} value - the value of the field
10
10
  */
11
11
 
12
12
  /**
@@ -15,13 +15,14 @@
15
15
  * @property {Object} rules - The rules you want to use through validation
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
- * @property {ValidatorOption} options - Options
18
+ * @property {ValidatorOptions} options - Options
19
19
  */
20
20
 
21
21
  /**
22
- * @typedef {Object} ValidatorOption
22
+ * @typedef {Object} ValidatorOptions
23
23
  * @property {boolean} propertiesMustMatch - If the form fields doesn't match with the expected structure will triggers an error
24
24
  * @property {boolean} abortEarly - Stops when caughts the first error
25
+ * @property {boolean} cache - Defines if the schema will uses cache as default or not
25
26
  */
26
27
 
27
28
  /**
@@ -39,13 +40,14 @@
39
40
 
40
41
  /**
41
42
  * @typedef {Object} SchemaRule
42
- * @property {SchemaRuleFiVeld} field - The field which will use the rule
43
+ * @property {SchemaRuleField} field - The field which will use the rule
43
44
  */
44
45
 
45
46
  /**
46
47
  * @typedef {Object} SchemaRuleField
47
48
  * @property {string} rule - The validation rule for the field (e.g., "name", "email", "phone", "hasText").
48
49
  * @property {boolean} required - Indicates whether the field is required.
50
+ * @property {boolean} cache - Indicates if the field requires cache or not
49
51
  */
50
52
 
51
53
  /**
@@ -65,5 +67,15 @@
65
67
  * @typedef {Object} DataValidatorErrorResponse
66
68
  * @property {boolean} error - Indicates an error occurred.
67
69
  * @property {string} [errorMessage] - A message describing the error (optional).
68
- * @property {Object} [dataErrors] - Additional error details (optional).
70
+ * @property {Object.<string, CheckError>} [errors] - Additional error details (optional).
71
+ */
72
+
73
+ /**
74
+ * Error Object
75
+ *
76
+ * @typedef {Object} CheckError
77
+ * @property {string} name - Field name
78
+ * @property {string} code - Error path (optional)
79
+ * @property {string} type - Error type (optional)
80
+ * @property {string} message - Error message (optional)
69
81
  */