pols-validator 1.0.1

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.
@@ -0,0 +1,22 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "node",
9
+ "request": "launch",
10
+ "name": "npm run test",
11
+ "skipFiles": [
12
+ "<node_internals>/**"
13
+ ],
14
+ "runtimeExecutable": "npm",
15
+ "runtimeArgs": [
16
+ "run",
17
+ "test",
18
+ "test/validaciones.ts"
19
+ ]
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "typescript.tsdk": "node_modules\\typescript\\lib"
3
+ }
@@ -0,0 +1,15 @@
1
+ import { Rules } from "./rules";
2
+ import { RulesEngine, RulesParams } from "./rulesEngine";
3
+ export { RulesEngine } from './rulesEngine';
4
+ export type EvaluateResponse<T> = {
5
+ error: false;
6
+ success: true;
7
+ result: T;
8
+ } | {
9
+ error: true;
10
+ success: false;
11
+ messages: string[];
12
+ };
13
+ export declare const validate: <T>(target: unknown, rules: RulesEngine) => EvaluateResponse<T>;
14
+ export declare const rules: (params?: RulesParams) => Rules;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAW,MAAM,eAAe,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IACjC,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,MAAM,EAAE,CAAC,CAAA;CACT,GAAG;IACH,KAAK,EAAE,IAAI,CAAA;IACX,OAAO,EAAE,KAAK,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,CAAC,UAAU,OAAO,SAAS,WAAW,KAAG,gBAAgB,CAAC,CAAC,CAqEnF,CAAA;AAED,eAAO,MAAM,KAAK,YAAa,WAAW,UAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = exports.validate = exports.RulesEngine = void 0;
4
+ const pols_utils_1 = require("pols-utils");
5
+ const rules_1 = require("./rules");
6
+ var rulesEngine_1 = require("./rulesEngine");
7
+ Object.defineProperty(exports, "RulesEngine", { enumerable: true, get: function () { return rulesEngine_1.RulesEngine; } });
8
+ const validate = (target, rules) => {
9
+ const errorMessages = [];
10
+ if (typeof target == 'string')
11
+ target = target.trim();
12
+ const isEmpty = target == null || (typeof target == 'string' && !target);
13
+ const label = rules.label ?? 'Este valor';
14
+ if (rules.required && isEmpty) {
15
+ return {
16
+ error: true,
17
+ success: false,
18
+ messages: [`'${label}' es requerido`]
19
+ };
20
+ }
21
+ if (isEmpty) {
22
+ return {
23
+ error: false,
24
+ success: true,
25
+ result: rules.default
26
+ };
27
+ }
28
+ const wrapper = {
29
+ value: pols_utils_1.PUtils.clone(target),
30
+ label
31
+ };
32
+ for (const validationFunction of Object.values(rules.collection)) {
33
+ const result = validationFunction(wrapper);
34
+ if (!result)
35
+ continue;
36
+ if (typeof result == 'string') {
37
+ errorMessages.push(result);
38
+ break;
39
+ }
40
+ else {
41
+ if (wrapper.value == null || typeof wrapper.value != 'object')
42
+ throw new Error(`El valor no es un objeto para ser validado contra un esquema: ${wrapper.value}`);
43
+ const newResult = {};
44
+ for (const key in result.schema) {
45
+ const rulesInside = result.schema[key];
46
+ const labelIndise = rulesInside.label ?? key;
47
+ rulesInside.label = `${result.prefix ? `${result.prefix} ` : ''}${labelIndise}`;
48
+ newResult[key] = wrapper.value[key];
49
+ const result2 = (0, exports.validate)(newResult[key], rulesInside);
50
+ if (result2.error == true) {
51
+ errorMessages.push(...result2.messages);
52
+ }
53
+ else {
54
+ newResult[key] = result2.result;
55
+ }
56
+ }
57
+ wrapper.value = newResult;
58
+ }
59
+ }
60
+ if (errorMessages.length) {
61
+ return {
62
+ error: true,
63
+ success: false,
64
+ messages: errorMessages
65
+ };
66
+ }
67
+ else {
68
+ return {
69
+ error: false,
70
+ success: true,
71
+ result: wrapper.value
72
+ };
73
+ }
74
+ };
75
+ exports.validate = validate;
76
+ const rules = (params) => new rules_1.Rules(params);
77
+ exports.rules = rules;
@@ -0,0 +1,37 @@
1
+ import { RulesEngine } from "./rulesEngine";
2
+ export declare class Rules extends RulesEngine {
3
+ isAlphanumeric(): this;
4
+ isEmailAddress(): this;
5
+ isDateTime(): this;
6
+ isDate(): this;
7
+ isTime(): this;
8
+ match(pattern: RegExp): this;
9
+ isNumber(): this;
10
+ isInteger(): this;
11
+ isNatural(): this;
12
+ isNaturalNoZero(): this;
13
+ onlyNumbers(): this;
14
+ maxLength(limit: number): this;
15
+ minLength(limit: number): this;
16
+ length(limit: number): this;
17
+ left(limit: number): this;
18
+ isIn(...elements: unknown[]): this;
19
+ isNotIn(...elements: unknown[]): this;
20
+ gt(limit: number): this;
21
+ gte(limit: number): this;
22
+ lt(limit: number): this;
23
+ lte(limit: number): this;
24
+ beforeOrSameAsNow(): this;
25
+ isObject(schema?: Record<string, RulesEngine>, prefix?: string): this;
26
+ isBoolean(): this;
27
+ upper(): this;
28
+ lower(): this;
29
+ decodeURI(): this;
30
+ round(decimals: number): this;
31
+ cleanDoubleSpaces(): this;
32
+ noSpaces(): this;
33
+ replace(search: string | RegExp, replace: string | ((substring: string, ...args: any[]) => string)): this;
34
+ capitalize(): this;
35
+ split(separator: string | RegExp): this;
36
+ }
37
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAW,MAAM,eAAe,CAAA;AAEpD,qBAAa,KAAM,SAAQ,WAAW;IACrC,cAAc;IAWd,cAAc;IAQd,UAAU;IAcV,MAAM;IAMN,MAAM;IA4BN,KAAK,CAAC,OAAO,EAAE,MAAM;IAQrB,QAAQ;IAUR,SAAS;IAQT,SAAS;IAIT,eAAe;IAIf,WAAW;IAQX,SAAS,CAAC,KAAK,EAAE,MAAM;IAgBvB,SAAS,CAAC,KAAK,EAAE,MAAM;IAQvB,MAAM,CAAC,KAAK,EAAE,MAAM;IAQpB,IAAI,CAAC,KAAK,EAAE,MAAM;IA0ElB,IAAI,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE;IAa3B,OAAO,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE;IAe9B,EAAE,CAAC,KAAK,EAAE,MAAM;IAQhB,GAAG,CAAC,KAAK,EAAE,MAAM;IAQjB,EAAE,CAAC,KAAK,EAAE,MAAM;IAQhB,GAAG,CAAC,KAAK,EAAE,MAAM;IAQjB,iBAAiB;IASjB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM;IAqB9D,SAAS;IAgDT,KAAK;IAQL,KAAK;IAQL,SAAS;IAQT,KAAK,CAAC,QAAQ,EAAE,MAAM;IAStB,iBAAiB;IAQjB,QAAQ;IASR,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC;IAMlG,UAAU;IAQV,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAKhC"}
package/dist/rules.js ADDED
@@ -0,0 +1,422 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Rules = void 0;
4
+ const pols_utils_1 = require("pols-utils");
5
+ const rulesEngine_1 = require("./rulesEngine");
6
+ class Rules extends rulesEngine_1.RulesEngine {
7
+ isAlphanumeric() {
8
+ this.add(this.isAlphanumeric.name, (wrapper) => {
9
+ if (typeof wrapper.value == 'number') {
10
+ wrapper.value = wrapper.value.toString();
11
+ }
12
+ else if (typeof wrapper.value != 'string') {
13
+ return `'${wrapper.label}' debe ser un alfanumérico`;
14
+ }
15
+ });
16
+ return this;
17
+ }
18
+ isEmailAddress() {
19
+ this.isAlphanumeric();
20
+ this.add(this.isEmailAddress.name, (wrapper) => {
21
+ if (!wrapper.value.match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/))
22
+ return `'${wrapper.label}' debe ser una dirección de correo`;
23
+ });
24
+ return this;
25
+ }
26
+ isDateTime() {
27
+ this.add(this.isDateTime.name, (wrapper) => {
28
+ const message = `'${wrapper.label}' tiene un formato de fecha y hora no válido`;
29
+ if (typeof wrapper.value == 'string' || typeof wrapper.value == 'number' || wrapper.value instanceof Date || wrapper.value instanceof pols_utils_1.PDate) {
30
+ const newDate = new pols_utils_1.PDate(wrapper.value);
31
+ if (newDate.isInvalidDate)
32
+ return message;
33
+ wrapper.value = newDate;
34
+ }
35
+ else {
36
+ return message;
37
+ }
38
+ });
39
+ return this;
40
+ }
41
+ isDate() {
42
+ return this.isDateTime().add(this.isDate.name, (wrapper) => {
43
+ wrapper.value.clearTime();
44
+ });
45
+ }
46
+ isTime() {
47
+ this.add(this.isTime.name, (wrapper) => {
48
+ const message = `'${wrapper.label}' contiene un formato de hora no válido`;
49
+ const value = wrapper.value.toString();
50
+ const parts = value.replace(/[.,]/g, ':').replace('m', '').match(/^([0-2]?[0-9])(:?)([0-5]?[0-9]?)(:?)([0-5]?[0-9]?)([ap]?)\.?m?\.?$/);
51
+ if (!parts)
52
+ return message;
53
+ let hours = Number(parts[1]);
54
+ const minutes = Number(parts[3]);
55
+ const seconds = Number(parts[5]);
56
+ const middle = parts[6];
57
+ if (minutes < 60 && seconds < 60) {
58
+ /* Si middle es diferente de vacío, es porque el usuario tiene la intención de especificar una hora del día, caso contrario, está indicando una duración. */
59
+ if (middle) {
60
+ if (hours > 0 && hours <= 12) {
61
+ if (middle === 'p')
62
+ hours += 12;
63
+ wrapper.value = `${pols_utils_1.PUtils.String.padLeft(hours, 2)}:${pols_utils_1.PUtils.String.padLeft(minutes, 2)}:${pols_utils_1.PUtils.String.padLeft(seconds, 2)}`;
64
+ return;
65
+ }
66
+ }
67
+ else {
68
+ wrapper.value = `${pols_utils_1.PUtils.String.padLeft(hours, 2)}:${pols_utils_1.PUtils.String.padLeft(minutes, 2)}:${pols_utils_1.PUtils.String.padLeft(seconds, 2)}`;
69
+ return;
70
+ }
71
+ }
72
+ return message;
73
+ });
74
+ return this;
75
+ }
76
+ match(pattern) {
77
+ this.isAlphanumeric();
78
+ this.add(this.match.name, (wrapper) => {
79
+ if (!wrapper.value.match(pattern))
80
+ return `'${wrapper.label}' no cumple con el formato de texto deseado`;
81
+ });
82
+ return this;
83
+ }
84
+ isNumber() {
85
+ this.add(this.isNumber.name, (wrapper) => {
86
+ const message = `'${wrapper.label}' debe ser un número`;
87
+ const value = Number(wrapper.value);
88
+ if (isNaN(value) || value == Infinity)
89
+ return message;
90
+ wrapper.value = value;
91
+ });
92
+ return this;
93
+ }
94
+ isInteger() {
95
+ this.isNumber();
96
+ this.add(this.isInteger.name, (wrapper) => {
97
+ if (wrapper.value != Math.floor(wrapper.value))
98
+ return `'${wrapper.label}' debe ser un número entero`;
99
+ });
100
+ return this;
101
+ }
102
+ isNatural() {
103
+ return this.isInteger().gte(0);
104
+ }
105
+ isNaturalNoZero() {
106
+ return this.isInteger().gt(0);
107
+ }
108
+ onlyNumbers() {
109
+ this.isAlphanumeric();
110
+ this.add(this.onlyNumbers.name, (wrapper) => {
111
+ if (!wrapper.value.match(/^[0-9]+$/))
112
+ return `'${wrapper.label}' debe contener sólo números`;
113
+ });
114
+ return this;
115
+ }
116
+ maxLength(limit) {
117
+ this.isAlphanumeric();
118
+ this.add(this.maxLength.name, (wrapper) => {
119
+ if (wrapper.value.length > limit)
120
+ return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como máximo`;
121
+ });
122
+ return this;
123
+ }
124
+ // maxCount(limit: number) {
125
+ // this.isArray()
126
+ // this.add(this.maxLength.name, (wrapper: Wrapper<unknown[]>) => {
127
+ // if (wrapper.value.length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'elemento' : 'elementos'}' como máximo`
128
+ // })
129
+ // return this
130
+ // }
131
+ minLength(limit) {
132
+ this.isAlphanumeric();
133
+ this.add(this.minLength.name, (wrapper) => {
134
+ if (wrapper.value.length < limit)
135
+ return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como mínimo`;
136
+ });
137
+ return this;
138
+ }
139
+ length(limit) {
140
+ this.isAlphanumeric();
141
+ this.add(this.length.name, (wrapper) => {
142
+ if (wrapper.value.length != limit)
143
+ return `'${wrapper.label}' debe contener sólo '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}'`;
144
+ });
145
+ return this;
146
+ }
147
+ left(limit) {
148
+ this.isAlphanumeric();
149
+ this.add(this.length.name, (wrapper) => {
150
+ wrapper.value = wrapper.value.substring(0, limit);
151
+ });
152
+ return this;
153
+ }
154
+ // isArray(checkingElements?: (i: number) => RulesEngine) {
155
+ // this.add(this.isArray.name, (wrapper: Wrapper) => {
156
+ // const message = `'${wrapper.label}' debe ser una lista de elementos`
157
+ // if (typeof wrapper.value == 'string') {
158
+ // try {
159
+ // const value = JSON.parse(wrapper.value)
160
+ // if (!(value instanceof Array)) return message
161
+ // wrapper.value = value
162
+ // } catch (err) {
163
+ // return message
164
+ // }
165
+ // } else {
166
+ // if (!(wrapper.value instanceof Array)) return message
167
+ // }
168
+ // if (checkingElements) {
169
+ // const value = wrapper.value as unknown[]
170
+ // const messageErrors: string[] = []
171
+ // const result: unknown[] = []
172
+ // for (const [i, t] of value.entries()) {
173
+ // const p = wrapper.validator({
174
+ // value: t
175
+ // }, {
176
+ // value: {
177
+ // ...checkingElements(i),
178
+ // to: undefined,
179
+ // }
180
+ // })
181
+ // if (p.error == true) {
182
+ // messageErrors.push(...p.messages)
183
+ // } else {
184
+ // result.push(p.result['value'])
185
+ // }
186
+ // }
187
+ // if (messageErrors.length) {
188
+ // return messageErrors
189
+ // } else {
190
+ // wrapper.value = result
191
+ // }
192
+ // }
193
+ // })
194
+ // return this
195
+ // }
196
+ // isArrayOfObjects(checkingElements?: (i: number) => FieldsStructure, prefixString?: (i: number) => string) {
197
+ // this.isArray()
198
+ // this.add(this.isArrayOfObjects.name, (wrapper: Wrapper<unknown[]>) => {
199
+ // const message = `'${wrapper.label}' debe ser una lista de objetos`
200
+ // const messages: string[] = []
201
+ // for (const [i, v] of wrapper.value.entries()) {
202
+ // const result = isObject(v)
203
+ // if (result.error == true) return message
204
+ // wrapper.value[i] = result.result
205
+ // if (checkingElements) {
206
+ // const v = wrapper.validator(wrapper.value[i], checkingElements(i), prefixString?.(i))
207
+ // if (v.error == true) {
208
+ // messages.push(...v.messages)
209
+ // } else {
210
+ // wrapper.value[i] = v.result
211
+ // }
212
+ // }
213
+ // }
214
+ // if (messages.length) return messages
215
+ // })
216
+ // return this
217
+ // }
218
+ isIn(...elements) {
219
+ this.add(this.isIn.name, (wrapper) => {
220
+ if (wrapper.value instanceof Array) {
221
+ for (const v of wrapper.value) {
222
+ if (!elements.includes(v))
223
+ return `'${wrapper.label}' no contiene valores válidos`;
224
+ }
225
+ }
226
+ else {
227
+ if (!elements.includes(wrapper.value))
228
+ return `'${wrapper.label}' no tiene un valor válido`;
229
+ }
230
+ });
231
+ return this;
232
+ }
233
+ isNotIn(...elements) {
234
+ this.add(this.isNotIn.name, (wrapper) => {
235
+ if (elements.includes(wrapper.value))
236
+ return `'${wrapper.label}' no tiene un valor válido`;
237
+ });
238
+ return this;
239
+ }
240
+ // hasElements(checkingElements?: (i: number) => FieldStructure) {
241
+ // this.isArray(checkingElements)
242
+ // this.add(this.hasElements.name, (wrapper: Wrapper) => {
243
+ // if (!(wrapper.value as unknown[]).length) return `'${wrapper.label}' debe ser una lista con al menos un elemento`
244
+ // })
245
+ // return this
246
+ // }
247
+ gt(limit) {
248
+ this.isNumber();
249
+ this.add(this.gt.name, (wrapper) => {
250
+ if (wrapper.value <= limit)
251
+ return `'${wrapper.label}' debe ser mayor a '${limit}'`;
252
+ });
253
+ return this;
254
+ }
255
+ gte(limit) {
256
+ this.isNumber();
257
+ this.add(this.gte.name, (wrapper) => {
258
+ if (wrapper.value < limit)
259
+ return `'${wrapper.label}' debe ser mayor o igual a '${limit}'`;
260
+ });
261
+ return this;
262
+ }
263
+ lt(limit) {
264
+ this.isNumber();
265
+ this.add(this.lt.name, (wrapper) => {
266
+ if (wrapper.value >= limit)
267
+ return `'${wrapper.label}' debe ser menor a '${limit}'`;
268
+ });
269
+ return this;
270
+ }
271
+ lte(limit) {
272
+ return this
273
+ .isNumber()
274
+ .add(this.lte.name, (wrapper) => {
275
+ if (wrapper.value > limit)
276
+ return `'${wrapper.label}' debe ser menor o igual a '${limit}'`;
277
+ });
278
+ }
279
+ beforeOrSameAsNow() {
280
+ return this
281
+ .isDateTime()
282
+ .add(this.beforeOrSameAsNow.name, (wrapper) => {
283
+ const now = new pols_utils_1.PDate;
284
+ if (wrapper.value.time > now.time)
285
+ return `'${wrapper.label}' debe ser anterior o igual a 'ahora'`;
286
+ });
287
+ }
288
+ isObject(schema, prefix) {
289
+ return this.add(this.isObject.name, (wrapper) => {
290
+ const message = `'${wrapper.label}' debe ser un objeto`;
291
+ if (typeof wrapper.value == 'string') {
292
+ try {
293
+ wrapper.value = JSON.parse(wrapper.value);
294
+ }
295
+ catch {
296
+ return message;
297
+ }
298
+ if (pols_utils_1.PUtils.getType(wrapper.value) != 'Object')
299
+ return message;
300
+ }
301
+ else {
302
+ if (pols_utils_1.PUtils.getType(wrapper.value) != 'Object') {
303
+ return message;
304
+ }
305
+ }
306
+ return { schema, prefix };
307
+ });
308
+ }
309
+ isBoolean() {
310
+ this.add(this.isBoolean.name, (wrapper) => {
311
+ const message = `'${wrapper.label}' debe ser de tipo booleano`;
312
+ if (typeof wrapper.value == 'string') {
313
+ const value = wrapper.value.trim().toUpperCase();
314
+ switch (value) {
315
+ case '1':
316
+ case 'S':
317
+ case 'SÍ':
318
+ case 'Y':
319
+ case 'YES':
320
+ wrapper.value = true;
321
+ return;
322
+ case '0':
323
+ case 'N':
324
+ case 'NO':
325
+ wrapper.value = false;
326
+ return;
327
+ default: {
328
+ let json;
329
+ try {
330
+ json = JSON.parse(wrapper.value);
331
+ }
332
+ catch {
333
+ return message;
334
+ }
335
+ if (typeof json != 'boolean' && !([0, 1]).includes(json))
336
+ return message;
337
+ wrapper.value = json;
338
+ return;
339
+ }
340
+ }
341
+ }
342
+ else if (typeof wrapper.value == 'number') {
343
+ switch (wrapper.value) {
344
+ case 0:
345
+ wrapper.value = false;
346
+ return;
347
+ case 1:
348
+ wrapper.value = true;
349
+ return;
350
+ default:
351
+ return message;
352
+ }
353
+ }
354
+ else {
355
+ return typeof wrapper.value == 'boolean' ? null : message;
356
+ }
357
+ });
358
+ return this;
359
+ }
360
+ upper() {
361
+ this.isAlphanumeric();
362
+ this.add(this.upper.name, (wrapper) => {
363
+ wrapper.value = wrapper.value.toUpperCase();
364
+ });
365
+ return this;
366
+ }
367
+ lower() {
368
+ this.isAlphanumeric();
369
+ this.add(this.lower.name, (wrapper) => {
370
+ wrapper.value = wrapper.value.toLowerCase();
371
+ });
372
+ return this;
373
+ }
374
+ decodeURI() {
375
+ this.isAlphanumeric();
376
+ this.add(this.decodeURI.name, (wrapper) => {
377
+ wrapper.value = decodeURI(wrapper.value);
378
+ });
379
+ return this;
380
+ }
381
+ round(decimals) {
382
+ this.isNumber();
383
+ this.add(this.round.name, (wrapper) => {
384
+ wrapper.value = pols_utils_1.PUtils.Number.round(wrapper.value, decimals);
385
+ });
386
+ return this;
387
+ }
388
+ /* Reemplaza todos los dobles (o más) espacios juntos por uno simple */
389
+ cleanDoubleSpaces() {
390
+ this.isAlphanumeric();
391
+ this.add(this.cleanDoubleSpaces.name, (wrapper) => {
392
+ wrapper.value = wrapper.value.replace(/\s{2,}/g, ' ');
393
+ });
394
+ return this;
395
+ }
396
+ noSpaces() {
397
+ this.isAlphanumeric();
398
+ this.add(this.noSpaces.name, (wrapper) => {
399
+ if (wrapper.value.match(/\s/))
400
+ return `'${wrapper.label}' no debe contener 'espacios'`;
401
+ });
402
+ return this;
403
+ }
404
+ replace(search, replace) {
405
+ return this.isAlphanumeric().add(this.replace.name, (wrapper) => {
406
+ wrapper.value = wrapper.value.replace(search, replace);
407
+ });
408
+ }
409
+ capitalize() {
410
+ this.isAlphanumeric();
411
+ this.add(this.capitalize.name, (wrapper) => {
412
+ wrapper.value = pols_utils_1.PUtils.String.capitalize(wrapper.value);
413
+ });
414
+ return this;
415
+ }
416
+ split(separator) {
417
+ return this.isAlphanumeric().add(this.split.name, (wrapper) => {
418
+ wrapper.value = wrapper.value.split(separator);
419
+ });
420
+ }
421
+ }
422
+ exports.Rules = Rules;
@@ -0,0 +1,24 @@
1
+ export type RulesParams = {
2
+ label?: string;
3
+ } & ({
4
+ required?: boolean;
5
+ } | {
6
+ default?: unknown;
7
+ });
8
+ export type ValidationFunction = (wrapper: Wrapper, ...args: unknown[]) => string | void | null | undefined | {
9
+ schema: Record<string, RulesEngine>;
10
+ prefix?: string;
11
+ };
12
+ export type Wrapper<T = unknown> = {
13
+ value: T;
14
+ label: string;
15
+ };
16
+ export declare class RulesEngine {
17
+ constructor(params?: RulesParams);
18
+ label: string;
19
+ required: boolean;
20
+ default: unknown;
21
+ collection: Record<string, ValidationFunction>;
22
+ protected add(name: string, validationFunction: ValidationFunction): this;
23
+ }
24
+ //# sourceMappingURL=rulesEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rulesEngine.d.ts","sourceRoot":"","sources":["../src/rulesEngine.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,CAAC;IACJ,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAC,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG;IAC7G,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,OAAO,IAAI;IAClC,KAAK,EAAE,CAAC,CAAA;IACR,KAAK,EAAE,MAAM,CAAA;CACb,CAAA;AAED,qBAAa,WAAW;gBACX,MAAM,CAAC,EAAE,WAAW;IAOhC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAQ;IACzB,OAAO,EAAE,OAAO,CAAO;IACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAK;IAEnD,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAE,kBAAkB;CAIlE"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RulesEngine = void 0;
4
+ class RulesEngine {
5
+ constructor(params) {
6
+ if (!params)
7
+ return;
8
+ this.label = params.label;
9
+ this.required = 'required' in params ? params.required : false;
10
+ this.default = 'default' in params ? params.default : null;
11
+ }
12
+ label;
13
+ required = false;
14
+ default = null;
15
+ collection = {};
16
+ add(name, validationFunction) {
17
+ if (!this.collection[name])
18
+ this.collection[name] = validationFunction;
19
+ return this;
20
+ }
21
+ }
22
+ exports.RulesEngine = RulesEngine;
@@ -0,0 +1,19 @@
1
+ import globals from "globals";
2
+ import pluginJs from "@eslint/js";
3
+ import tseslint from "typescript-eslint";
4
+
5
+ export default [
6
+ {
7
+ files: ["**/*.{ts}"],
8
+ },
9
+ { languageOptions: { globals: globals.node } },
10
+ pluginJs.configs.recommended,
11
+ ...tseslint.configs.recommended,
12
+ {
13
+ rules: {
14
+ "@typescript-eslint/no-unused-vars": "warn",
15
+ "@typescript-eslint/no-explicit-any": false,
16
+ "no-use-before-define": "off"
17
+ }
18
+ }
19
+ ];
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "pols-validator",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "npx tsc",
9
+ "test": "npx ts-node-dev -r tsconfig-paths/register --project tsconfig.json"
10
+ },
11
+ "author": "Jean Paul Sánchez mendoza",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "devDependencies": {
15
+ "@eslint/js": "^9.13.0",
16
+ "@types/node": "^22.7.9",
17
+ "eslint": "^9.13.0",
18
+ "globals": "^15.11.0",
19
+ "tsconfig-paths": "^4.2.0",
20
+ "typescript": "^5.6.3",
21
+ "typescript-eslint": "^8.11.0"
22
+ },
23
+ "dependencies": {
24
+ "pols-utils": "^1.1.2"
25
+ }
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ import { PUtils } from "pols-utils"
2
+ import { Rules } from "./rules"
3
+ import { RulesEngine, RulesParams, Wrapper } from "./rulesEngine"
4
+ export { RulesEngine } from './rulesEngine'
5
+
6
+ export type EvaluateResponse<T> = {
7
+ error: false
8
+ success: true
9
+ result: T
10
+ } | {
11
+ error: true
12
+ success: false
13
+ messages: string[]
14
+ }
15
+
16
+ export const validate = <T>(target: unknown, rules: RulesEngine): EvaluateResponse<T> => {
17
+ const errorMessages: string[] = []
18
+
19
+ if (typeof target == 'string') target = target.trim()
20
+
21
+ const isEmpty = target == null || (typeof target == 'string' && !target)
22
+ const label = rules.label ?? 'Este valor'
23
+
24
+ if (rules.required && isEmpty) {
25
+ return {
26
+ error: true,
27
+ success: false,
28
+ messages: [`'${label}' es requerido`]
29
+ }
30
+ }
31
+
32
+ if (isEmpty) {
33
+ return {
34
+ error: false,
35
+ success: true,
36
+ result: rules.default as T
37
+ }
38
+ }
39
+
40
+ const wrapper: Wrapper<T> = {
41
+ value: PUtils.clone(target) as T,
42
+ label
43
+ }
44
+ for (const validationFunction of Object.values(rules.collection)) {
45
+ const result = validationFunction(wrapper)
46
+ if (!result) continue
47
+ if (typeof result == 'string') {
48
+ errorMessages.push(result)
49
+ break
50
+ } else {
51
+ if (wrapper.value == null || typeof wrapper.value != 'object') throw new Error(`El valor no es un objeto para ser validado contra un esquema: ${wrapper.value}`)
52
+
53
+ const newResult: Record<string, unknown> = {}
54
+ for (const key in result.schema) {
55
+ const rulesInside = result.schema[key]
56
+ const labelIndise = rulesInside.label ?? key
57
+ rulesInside.label = `${result.prefix ? `${result.prefix} ` : ''}${labelIndise}`
58
+
59
+ newResult[key] = wrapper.value[key]
60
+
61
+ const result2 = validate(newResult[key], rulesInside)
62
+ if (result2.error == true) {
63
+ errorMessages.push(...result2.messages)
64
+ } else {
65
+ newResult[key] = result2.result
66
+ }
67
+ }
68
+ wrapper.value = newResult as T
69
+ }
70
+ }
71
+
72
+ if (errorMessages.length) {
73
+ return {
74
+ error: true,
75
+ success: false,
76
+ messages: errorMessages
77
+ }
78
+ } else {
79
+ return {
80
+ error: false,
81
+ success: true,
82
+ result: wrapper.value
83
+ }
84
+ }
85
+ }
86
+
87
+ export const rules = (params?: RulesParams) => new Rules(params)
package/src/rules.ts ADDED
@@ -0,0 +1,427 @@
1
+ import { PDate, PUtils } from "pols-utils"
2
+ import { RulesEngine, Wrapper } from "./rulesEngine"
3
+
4
+ export class Rules extends RulesEngine {
5
+ isAlphanumeric() {
6
+ this.add(this.isAlphanumeric.name, (wrapper: Wrapper) => {
7
+ if (typeof wrapper.value == 'number') {
8
+ wrapper.value = wrapper.value.toString()
9
+ } else if (typeof wrapper.value != 'string') {
10
+ return `'${wrapper.label}' debe ser un alfanumérico`
11
+ }
12
+ })
13
+ return this
14
+ }
15
+
16
+ isEmailAddress() {
17
+ this.isAlphanumeric()
18
+ this.add(this.isEmailAddress.name, (wrapper: Wrapper) => {
19
+ if (!(wrapper.value as string).match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/)) return `'${wrapper.label}' debe ser una dirección de correo`
20
+ })
21
+ return this
22
+ }
23
+
24
+ isDateTime() {
25
+ this.add(this.isDateTime.name, (wrapper: Wrapper) => {
26
+ const message = `'${wrapper.label}' tiene un formato de fecha y hora no válido`
27
+ if (typeof wrapper.value == 'string' || typeof wrapper.value == 'number' || wrapper.value instanceof Date || wrapper.value instanceof PDate) {
28
+ const newDate = new PDate(wrapper.value)
29
+ if (newDate.isInvalidDate) return message
30
+ wrapper.value = newDate
31
+ } else {
32
+ return message
33
+ }
34
+ })
35
+ return this
36
+ }
37
+
38
+ isDate() {
39
+ return this.isDateTime().add(this.isDate.name, (wrapper: Wrapper<PDate>) => {
40
+ wrapper.value.clearTime()
41
+ })
42
+ }
43
+
44
+ isTime() {
45
+ this.add(this.isTime.name, (wrapper: Wrapper) => {
46
+ const message = `'${wrapper.label}' contiene un formato de hora no válido`
47
+ const value = wrapper.value.toString()
48
+ const parts = value.replace(/[.,]/g, ':').replace('m', '').match(/^([0-2]?[0-9])(:?)([0-5]?[0-9]?)(:?)([0-5]?[0-9]?)([ap]?)\.?m?\.?$/)
49
+ if (!parts) return message
50
+ let hours = Number(parts[1])
51
+ const minutes = Number(parts[3])
52
+ const seconds = Number(parts[5])
53
+ const middle = parts[6]
54
+ if (minutes < 60 && seconds < 60) {
55
+ /* Si middle es diferente de vacío, es porque el usuario tiene la intención de especificar una hora del día, caso contrario, está indicando una duración. */
56
+ if (middle) {
57
+ if (hours > 0 && hours <= 12) {
58
+ if (middle === 'p') hours += 12
59
+ wrapper.value = `${PUtils.String.padLeft(hours, 2)}:${PUtils.String.padLeft(minutes, 2)}:${PUtils.String.padLeft(seconds, 2)}`
60
+ return
61
+ }
62
+ } else {
63
+ wrapper.value = `${PUtils.String.padLeft(hours, 2)}:${PUtils.String.padLeft(minutes, 2)}:${PUtils.String.padLeft(seconds, 2)}`
64
+ return
65
+ }
66
+ }
67
+ return message
68
+ })
69
+ return this
70
+ }
71
+
72
+ match(pattern: RegExp) {
73
+ this.isAlphanumeric()
74
+ this.add(this.match.name, (wrapper: Wrapper) => {
75
+ if (!(wrapper.value as string).match(pattern)) return `'${wrapper.label}' no cumple con el formato de texto deseado`
76
+ })
77
+ return this
78
+ }
79
+
80
+ isNumber() {
81
+ this.add(this.isNumber.name, (wrapper: Wrapper) => {
82
+ const message = `'${wrapper.label}' debe ser un número`
83
+ const value = Number(wrapper.value)
84
+ if (isNaN(value) || value == Infinity) return message
85
+ wrapper.value = value
86
+ })
87
+ return this
88
+ }
89
+
90
+ isInteger() {
91
+ this.isNumber()
92
+ this.add(this.isInteger.name, (wrapper: Wrapper) => {
93
+ if (wrapper.value != Math.floor(wrapper.value as number)) return `'${wrapper.label}' debe ser un número entero`
94
+ })
95
+ return this
96
+ }
97
+
98
+ isNatural() {
99
+ return this.isInteger().gte(0)
100
+ }
101
+
102
+ isNaturalNoZero() {
103
+ return this.isInteger().gt(0)
104
+ }
105
+
106
+ onlyNumbers() {
107
+ this.isAlphanumeric()
108
+ this.add(this.onlyNumbers.name, (wrapper: Wrapper) => {
109
+ if (!(wrapper.value as string).match(/^[0-9]+$/)) return `'${wrapper.label}' debe contener sólo números`
110
+ })
111
+ return this
112
+ }
113
+
114
+ maxLength(limit: number) {
115
+ this.isAlphanumeric()
116
+ this.add(this.maxLength.name, (wrapper: Wrapper) => {
117
+ if ((wrapper.value as string).length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como máximo`
118
+ })
119
+ return this
120
+ }
121
+
122
+ // maxCount(limit: number) {
123
+ // this.isArray()
124
+ // this.add(this.maxLength.name, (wrapper: Wrapper<unknown[]>) => {
125
+ // if (wrapper.value.length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'elemento' : 'elementos'}' como máximo`
126
+ // })
127
+ // return this
128
+ // }
129
+
130
+ minLength(limit: number) {
131
+ this.isAlphanumeric()
132
+ this.add(this.minLength.name, (wrapper: Wrapper) => {
133
+ if ((wrapper.value as string).length < limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como mínimo`
134
+ })
135
+ return this
136
+ }
137
+
138
+ length(limit: number) {
139
+ this.isAlphanumeric()
140
+ this.add(this.length.name, (wrapper: Wrapper) => {
141
+ if ((wrapper.value as string).length != limit) return `'${wrapper.label}' debe contener sólo '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}'`
142
+ })
143
+ return this
144
+ }
145
+
146
+ left(limit: number) {
147
+ this.isAlphanumeric()
148
+ this.add(this.length.name, (wrapper: Wrapper) => {
149
+ (wrapper.value as string) = (wrapper.value as string).substring(0, limit)
150
+ })
151
+ return this
152
+ }
153
+
154
+ // isArray(checkingElements?: (i: number) => RulesEngine) {
155
+ // this.add(this.isArray.name, (wrapper: Wrapper) => {
156
+ // const message = `'${wrapper.label}' debe ser una lista de elementos`
157
+ // if (typeof wrapper.value == 'string') {
158
+ // try {
159
+ // const value = JSON.parse(wrapper.value)
160
+ // if (!(value instanceof Array)) return message
161
+ // wrapper.value = value
162
+ // } catch (err) {
163
+ // return message
164
+ // }
165
+ // } else {
166
+ // if (!(wrapper.value instanceof Array)) return message
167
+ // }
168
+ // if (checkingElements) {
169
+ // const value = wrapper.value as unknown[]
170
+ // const messageErrors: string[] = []
171
+ // const result: unknown[] = []
172
+ // for (const [i, t] of value.entries()) {
173
+ // const p = wrapper.validator({
174
+ // value: t
175
+ // }, {
176
+ // value: {
177
+ // ...checkingElements(i),
178
+ // to: undefined,
179
+ // }
180
+ // })
181
+ // if (p.error == true) {
182
+ // messageErrors.push(...p.messages)
183
+ // } else {
184
+ // result.push(p.result['value'])
185
+ // }
186
+ // }
187
+ // if (messageErrors.length) {
188
+ // return messageErrors
189
+ // } else {
190
+ // wrapper.value = result
191
+ // }
192
+ // }
193
+ // })
194
+ // return this
195
+ // }
196
+
197
+ // isArrayOfObjects(checkingElements?: (i: number) => FieldsStructure, prefixString?: (i: number) => string) {
198
+ // this.isArray()
199
+ // this.add(this.isArrayOfObjects.name, (wrapper: Wrapper<unknown[]>) => {
200
+ // const message = `'${wrapper.label}' debe ser una lista de objetos`
201
+ // const messages: string[] = []
202
+ // for (const [i, v] of wrapper.value.entries()) {
203
+ // const result = isObject(v)
204
+ // if (result.error == true) return message
205
+ // wrapper.value[i] = result.result
206
+ // if (checkingElements) {
207
+ // const v = wrapper.validator(wrapper.value[i], checkingElements(i), prefixString?.(i))
208
+ // if (v.error == true) {
209
+ // messages.push(...v.messages)
210
+ // } else {
211
+ // wrapper.value[i] = v.result
212
+ // }
213
+ // }
214
+ // }
215
+ // if (messages.length) return messages
216
+ // })
217
+ // return this
218
+ // }
219
+
220
+ isIn(...elements: unknown[]) {
221
+ this.add(this.isIn.name, (wrapper: Wrapper) => {
222
+ if (wrapper.value instanceof Array) {
223
+ for (const v of wrapper.value) {
224
+ if (!elements.includes(v)) return `'${wrapper.label}' no contiene valores válidos`
225
+ }
226
+ } else {
227
+ if (!elements.includes(wrapper.value)) return `'${wrapper.label}' no tiene un valor válido`
228
+ }
229
+ })
230
+ return this
231
+ }
232
+
233
+ isNotIn(...elements: unknown[]) {
234
+ this.add(this.isNotIn.name, (wrapper: Wrapper) => {
235
+ if (elements.includes(wrapper.value)) return `'${wrapper.label}' no tiene un valor válido`
236
+ })
237
+ return this
238
+ }
239
+
240
+ // hasElements(checkingElements?: (i: number) => FieldStructure) {
241
+ // this.isArray(checkingElements)
242
+ // this.add(this.hasElements.name, (wrapper: Wrapper) => {
243
+ // if (!(wrapper.value as unknown[]).length) return `'${wrapper.label}' debe ser una lista con al menos un elemento`
244
+ // })
245
+ // return this
246
+ // }
247
+
248
+ gt(limit: number) {
249
+ this.isNumber()
250
+ this.add(this.gt.name, (wrapper: Wrapper) => {
251
+ if (wrapper.value as number <= limit) return `'${wrapper.label}' debe ser mayor a '${limit}'`
252
+ })
253
+ return this
254
+ }
255
+
256
+ gte(limit: number) {
257
+ this.isNumber()
258
+ this.add(this.gte.name, (wrapper: Wrapper) => {
259
+ if (wrapper.value as number < limit) return `'${wrapper.label}' debe ser mayor o igual a '${limit}'`
260
+ })
261
+ return this
262
+ }
263
+
264
+ lt(limit: number) {
265
+ this.isNumber()
266
+ this.add(this.lt.name, (wrapper: Wrapper) => {
267
+ if (wrapper.value as number >= limit) return `'${wrapper.label}' debe ser menor a '${limit}'`
268
+ })
269
+ return this
270
+ }
271
+
272
+ lte(limit: number) {
273
+ return this
274
+ .isNumber()
275
+ .add(this.lte.name, (wrapper: Wrapper) => {
276
+ if (wrapper.value as number > limit) return `'${wrapper.label}' debe ser menor o igual a '${limit}'`
277
+ })
278
+ }
279
+
280
+ beforeOrSameAsNow() {
281
+ return this
282
+ .isDateTime()
283
+ .add(this.beforeOrSameAsNow.name, (wrapper: Wrapper<PDate>) => {
284
+ const now = new PDate
285
+ if (wrapper.value.time > now.time) return `'${wrapper.label}' debe ser anterior o igual a 'ahora'`
286
+ })
287
+ }
288
+
289
+ isObject(schema?: Record<string, RulesEngine>, prefix?: string) {
290
+ return this.add(this.isObject.name, (wrapper: Wrapper) => {
291
+ const message = `'${wrapper.label}' debe ser un objeto`
292
+
293
+ if (typeof wrapper.value == 'string') {
294
+ try {
295
+ wrapper.value = JSON.parse(wrapper.value)
296
+ } catch {
297
+ return message
298
+ }
299
+ if (PUtils.getType(wrapper.value) != 'Object') return message
300
+ } else {
301
+ if (PUtils.getType(wrapper.value) != 'Object') {
302
+ return message
303
+ }
304
+ }
305
+
306
+ return { schema, prefix }
307
+ })
308
+ }
309
+
310
+ isBoolean() {
311
+ this.add(this.isBoolean.name, (wrapper: Wrapper) => {
312
+ const message = `'${wrapper.label}' debe ser de tipo booleano`
313
+ if (typeof wrapper.value == 'string') {
314
+ const value = wrapper.value.trim().toUpperCase()
315
+ switch (value) {
316
+ case '1':
317
+ case 'S':
318
+ case 'SÍ':
319
+ case 'Y':
320
+ case 'YES':
321
+ wrapper.value = true
322
+ return
323
+ case '0':
324
+ case 'N':
325
+ case 'NO':
326
+ wrapper.value = false
327
+ return
328
+ default: {
329
+ let json
330
+ try {
331
+ json = JSON.parse(wrapper.value)
332
+ } catch {
333
+ return message
334
+ }
335
+ if (typeof json != 'boolean' && !([0, 1]).includes(json)) return message
336
+ wrapper.value = json
337
+ return
338
+ }
339
+ }
340
+ } else if (typeof wrapper.value == 'number') {
341
+ switch (wrapper.value) {
342
+ case 0:
343
+ wrapper.value = false
344
+ return
345
+ case 1:
346
+ wrapper.value = true
347
+ return
348
+ default:
349
+ return message
350
+ }
351
+ } else {
352
+ return typeof wrapper.value == 'boolean' ? null : message
353
+ }
354
+ })
355
+ return this
356
+ }
357
+
358
+ upper() {
359
+ this.isAlphanumeric()
360
+ this.add(this.upper.name, (wrapper: Wrapper) => {
361
+ wrapper.value = (wrapper.value as string).toUpperCase()
362
+ })
363
+ return this
364
+ }
365
+
366
+ lower() {
367
+ this.isAlphanumeric()
368
+ this.add(this.lower.name, (wrapper: Wrapper) => {
369
+ wrapper.value = (wrapper.value as string).toLowerCase()
370
+ })
371
+ return this
372
+ }
373
+
374
+ decodeURI() {
375
+ this.isAlphanumeric()
376
+ this.add(this.decodeURI.name, (wrapper: Wrapper) => {
377
+ wrapper.value = decodeURI(wrapper.value as string)
378
+ })
379
+ return this
380
+ }
381
+
382
+ round(decimals: number) {
383
+ this.isNumber()
384
+ this.add(this.round.name, (wrapper: Wrapper) => {
385
+ wrapper.value = PUtils.Number.round(wrapper.value as number, decimals)
386
+ })
387
+ return this
388
+ }
389
+
390
+ /* Reemplaza todos los dobles (o más) espacios juntos por uno simple */
391
+ cleanDoubleSpaces() {
392
+ this.isAlphanumeric()
393
+ this.add(this.cleanDoubleSpaces.name, (wrapper: Wrapper) => {
394
+ wrapper.value = (wrapper.value as string).replace(/\s{2,}/g, ' ')
395
+ })
396
+ return this
397
+ }
398
+
399
+ noSpaces() {
400
+ this.isAlphanumeric()
401
+ this.add(this.noSpaces.name, (wrapper: Wrapper) => {
402
+ if ((wrapper.value as string).match(/\s/)) return `'${wrapper.label}' no debe contener 'espacios'`
403
+ })
404
+ return this
405
+ }
406
+
407
+
408
+ replace(search: string | RegExp, replace: string | ((substring: string, ...args: any[]) => string)) {
409
+ return this.isAlphanumeric().add(this.replace.name, (wrapper: Wrapper) => {
410
+ wrapper.value = (wrapper.value as string).replace(search, replace as any)
411
+ })
412
+ }
413
+
414
+ capitalize() {
415
+ this.isAlphanumeric()
416
+ this.add(this.capitalize.name, (wrapper: Wrapper) => {
417
+ wrapper.value = PUtils.String.capitalize(wrapper.value as string)
418
+ })
419
+ return this
420
+ }
421
+
422
+ split(separator: string | RegExp) {
423
+ return this.isAlphanumeric().add(this.split.name, (wrapper: Wrapper) => {
424
+ wrapper.value = (wrapper.value as any).split(separator)
425
+ })
426
+ }
427
+ }
@@ -0,0 +1,36 @@
1
+ export type RulesParams = {
2
+ label?: string
3
+ } & ({
4
+ required?: boolean
5
+ } | {
6
+ default?: unknown
7
+ })
8
+
9
+ export type ValidationFunction = (wrapper: Wrapper, ...args: unknown[]) => string | void | null | undefined | {
10
+ schema: Record<string, RulesEngine>
11
+ prefix?: string
12
+ }
13
+
14
+ export type Wrapper<T = unknown> = {
15
+ value: T
16
+ label: string
17
+ }
18
+
19
+ export class RulesEngine {
20
+ constructor(params?: RulesParams) {
21
+ if (!params) return
22
+ this.label = params.label
23
+ this.required = 'required' in params ? params.required : false
24
+ this.default = 'default' in params ? params.default : null
25
+ }
26
+
27
+ label: string
28
+ required: boolean = false
29
+ default: unknown = null
30
+ collection: Record<string, ValidationFunction> = {}
31
+
32
+ protected add(name: string, validationFunction: ValidationFunction) {
33
+ if (!this.collection[name]) this.collection[name] = validationFunction
34
+ return this
35
+ }
36
+ }
@@ -0,0 +1,22 @@
1
+ import { rules, validate } from '../src/index'
2
+
3
+ const original = {
4
+ uno: 'hola',
5
+ dos: 1,
6
+ tres: '67',
7
+ cuatro: {
8
+ aaa: '2024-10-28 23:56:00',
9
+ bbb: 'fgh'
10
+ }
11
+ }
12
+
13
+ const resultados = validate(original, rules({ label: 'Mi número' }).isObject({
14
+ uno: rules({ label: 'Uno', required: true }),
15
+ dos: rules().isBoolean(),
16
+ tres: rules({ required: true }).isNaturalNoZero(),
17
+ cuatro: rules({ label: 'Cuatro', default: {} }).isObject({
18
+ aaa: rules().isDateTime()
19
+ })
20
+ }, `Mi número >`))
21
+
22
+ console.log(original, resultados)
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": false,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "outDir": "./dist"
13
+ },
14
+ "include": [
15
+ "src/**/*.ts"
16
+ ],
17
+ "exclude": [
18
+ "node_modules"
19
+ ]
20
+ }