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 +33 -12
- package/bin/generate-docs.js +434 -0
- package/dist/main.cjs.js +1 -1
- package/package.json +5 -1
- package/src/index.d.ts +128 -0
- package/src/js/dataValidate.js +18 -8
- package/src/types.js +17 -5
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
|
|
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
|
-
```
|
|
156
|
+
```typescript
|
|
153
157
|
{
|
|
154
|
-
error: true
|
|
158
|
+
error: true,
|
|
155
159
|
errors: {
|
|
156
160
|
[field: string]: {
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
354
|
-
|
|
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
|
|
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.
|
|
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
|
+
}
|
package/src/js/dataValidate.js
CHANGED
|
@@ -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]}
|
|
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(
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
18
|
+
* @property {ValidatorOptions} options - Options
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @typedef {Object}
|
|
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 {
|
|
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} [
|
|
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
|
*/
|