cr-static-shared-components 9.9.0
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/CHANGELOG.md +53 -0
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/index.d.ts +58 -0
- package/index.js +36 -0
- package/package.json +49 -0
- package/src/async.js +229 -0
- package/src/crypto.js +216 -0
- package/src/errors.js +79 -0
- package/src/formatting.js +299 -0
- package/src/network.js +216 -0
- package/src/transformation.js +273 -0
- package/src/validation.js +403 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validation Module
|
|
5
|
+
* Provides comprehensive type checking, schema validation, and data sanitization
|
|
6
|
+
*
|
|
7
|
+
* @module validation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const errors = require('./errors');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Type validators
|
|
14
|
+
*/
|
|
15
|
+
const TYPE_VALIDATORS = {
|
|
16
|
+
string: (val) => typeof val === 'string',
|
|
17
|
+
number: (val) => typeof val === 'number' && !isNaN(val),
|
|
18
|
+
boolean: (val) => typeof val === 'boolean',
|
|
19
|
+
object: (val) => typeof val === 'object' && val !== null && !Array.isArray(val),
|
|
20
|
+
array: (val) => Array.isArray(val),
|
|
21
|
+
function: (val) => typeof val === 'function',
|
|
22
|
+
null: (val) => val === null,
|
|
23
|
+
undefined: (val) => val === undefined,
|
|
24
|
+
date: (val) => val instanceof Date && !isNaN(val.getTime()),
|
|
25
|
+
regexp: (val) => val instanceof RegExp,
|
|
26
|
+
symbol: (val) => typeof val === 'symbol',
|
|
27
|
+
bigint: (val) => typeof val === 'bigint'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Email validation (RFC 5322 compliant)
|
|
32
|
+
*/
|
|
33
|
+
function email(value) {
|
|
34
|
+
if (typeof value !== 'string') return false;
|
|
35
|
+
|
|
36
|
+
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
37
|
+
|
|
38
|
+
if (!emailRegex.test(value)) return false;
|
|
39
|
+
|
|
40
|
+
// Additional checks
|
|
41
|
+
const [local, domain] = value.split('@');
|
|
42
|
+
if (local.length > 64) return false;
|
|
43
|
+
if (domain.length > 255) return false;
|
|
44
|
+
|
|
45
|
+
// Check for consecutive dots
|
|
46
|
+
if (value.includes('..')) return false;
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* URL validation
|
|
53
|
+
*/
|
|
54
|
+
function url(value, options = {}) {
|
|
55
|
+
if (typeof value !== 'string') return false;
|
|
56
|
+
|
|
57
|
+
const defaults = {
|
|
58
|
+
protocols: ['http', 'https', 'ftp'],
|
|
59
|
+
requireProtocol: true
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const opts = { ...defaults, ...options };
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(value);
|
|
66
|
+
|
|
67
|
+
if (opts.requireProtocol && !parsed.protocol) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (opts.protocols.length > 0) {
|
|
72
|
+
const protocol = parsed.protocol.replace(':', '');
|
|
73
|
+
if (!opts.protocols.includes(protocol)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* String validation with options
|
|
86
|
+
*/
|
|
87
|
+
function string(value, options = {}) {
|
|
88
|
+
if (typeof value !== 'string') return false;
|
|
89
|
+
|
|
90
|
+
if (options.minLength && value.length < options.minLength) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.maxLength && value.length > options.maxLength) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.pattern && !new RegExp(options.pattern).test(value)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (options.enum && !options.enum.includes(value)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Number validation with constraints
|
|
111
|
+
*/
|
|
112
|
+
function number(value, options = {}) {
|
|
113
|
+
if (typeof value !== 'number' || isNaN(value)) return false;
|
|
114
|
+
|
|
115
|
+
if (options.min !== undefined && value < options.min) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (options.max !== undefined && value > options.max) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (options.integer && !Number.isInteger(value)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (options.positive && value <= 0) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.negative && value >= 0) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Array validation
|
|
140
|
+
*/
|
|
141
|
+
function array(itemType, data) {
|
|
142
|
+
if (!Array.isArray(data)) {
|
|
143
|
+
return { valid: false, errors: ['Value is not an array'] };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!TYPE_VALIDATORS[itemType]) {
|
|
147
|
+
return { valid: false, errors: [`Unknown type: ${itemType}`] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const validator = TYPE_VALIDATORS[itemType];
|
|
151
|
+
const errors = [];
|
|
152
|
+
|
|
153
|
+
data.forEach((item, index) => {
|
|
154
|
+
if (!validator(item)) {
|
|
155
|
+
errors.push(`Item at index ${index} is not of type ${itemType}`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
valid: errors.length === 0,
|
|
161
|
+
errors: errors.length > 0 ? errors : undefined
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Object schema validation
|
|
167
|
+
*/
|
|
168
|
+
function object(schema, data) {
|
|
169
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
170
|
+
return { valid: false, errors: ['Value is not an object'] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const errors = [];
|
|
174
|
+
|
|
175
|
+
// Check required fields
|
|
176
|
+
for (const [key, typeOrSchema] of Object.entries(schema)) {
|
|
177
|
+
if (!(key in data)) {
|
|
178
|
+
errors.push(`Missing required field: ${key}`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const value = data[key];
|
|
183
|
+
|
|
184
|
+
// Nested schema
|
|
185
|
+
if (typeof typeOrSchema === 'object' && !Array.isArray(typeOrSchema)) {
|
|
186
|
+
const result = object(typeOrSchema, value);
|
|
187
|
+
if (!result.valid) {
|
|
188
|
+
errors.push(`Invalid nested object at ${key}: ${result.errors.join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Type validation
|
|
194
|
+
if (typeof typeOrSchema === 'string') {
|
|
195
|
+
// Special types
|
|
196
|
+
if (typeOrSchema === 'email') {
|
|
197
|
+
if (!email(value)) {
|
|
198
|
+
errors.push(`Field ${key} is not a valid email`);
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (typeOrSchema === 'url') {
|
|
204
|
+
if (!url(value)) {
|
|
205
|
+
errors.push(`Field ${key} is not a valid URL`);
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Standard types
|
|
211
|
+
const validator = TYPE_VALIDATORS[typeOrSchema];
|
|
212
|
+
if (!validator) {
|
|
213
|
+
errors.push(`Unknown type for field ${key}: ${typeOrSchema}`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!validator(value)) {
|
|
218
|
+
errors.push(`Field ${key} is not of type ${typeOrSchema}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
valid: errors.length === 0,
|
|
225
|
+
errors: errors.length > 0 ? errors : undefined
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* UUID validation
|
|
231
|
+
*/
|
|
232
|
+
function uuid(value, version = null) {
|
|
233
|
+
if (typeof value !== 'string') return false;
|
|
234
|
+
|
|
235
|
+
const patterns = {
|
|
236
|
+
3: /^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
237
|
+
4: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
238
|
+
5: /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
239
|
+
all: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const pattern = version ? patterns[version] : patterns.all;
|
|
243
|
+
return pattern ? pattern.test(value) : false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* IP address validation
|
|
248
|
+
*/
|
|
249
|
+
function ip(value, version = null) {
|
|
250
|
+
if (typeof value !== 'string') return false;
|
|
251
|
+
|
|
252
|
+
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
253
|
+
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^[0-9a-fA-F]{1,4}::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$/;
|
|
254
|
+
|
|
255
|
+
if (version === 4) return ipv4Regex.test(value);
|
|
256
|
+
if (version === 6) return ipv6Regex.test(value);
|
|
257
|
+
|
|
258
|
+
return ipv4Regex.test(value) || ipv6Regex.test(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Credit card validation (Luhn algorithm)
|
|
263
|
+
*/
|
|
264
|
+
function creditCard(value) {
|
|
265
|
+
if (typeof value !== 'string') return false;
|
|
266
|
+
|
|
267
|
+
const sanitized = value.replace(/[\s-]/g, '');
|
|
268
|
+
if (!/^\d{13,19}$/.test(sanitized)) return false;
|
|
269
|
+
|
|
270
|
+
let sum = 0;
|
|
271
|
+
let shouldDouble = false;
|
|
272
|
+
|
|
273
|
+
for (let i = sanitized.length - 1; i >= 0; i--) {
|
|
274
|
+
let digit = parseInt(sanitized.charAt(i), 10);
|
|
275
|
+
|
|
276
|
+
if (shouldDouble) {
|
|
277
|
+
digit *= 2;
|
|
278
|
+
if (digit > 9) digit -= 9;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
sum += digit;
|
|
282
|
+
shouldDouble = !shouldDouble;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return sum % 10 === 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Date range validation
|
|
290
|
+
*/
|
|
291
|
+
function dateRange(value, options = {}) {
|
|
292
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
293
|
+
|
|
294
|
+
if (isNaN(date.getTime())) return false;
|
|
295
|
+
|
|
296
|
+
if (options.min) {
|
|
297
|
+
const minDate = options.min instanceof Date ? options.min : new Date(options.min);
|
|
298
|
+
if (date < minDate) return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (options.max) {
|
|
302
|
+
const maxDate = options.max instanceof Date ? options.max : new Date(options.max);
|
|
303
|
+
if (date > maxDate) return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Phone number validation (international format)
|
|
311
|
+
*/
|
|
312
|
+
function phone(value, format = 'international') {
|
|
313
|
+
if (typeof value !== 'string') return false;
|
|
314
|
+
|
|
315
|
+
if (format === 'international') {
|
|
316
|
+
// E.164 format
|
|
317
|
+
return /^\+[1-9]\d{1,14}$/.test(value);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (format === 'us') {
|
|
321
|
+
return /^(\+1)?[-.\s]?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}$/.test(value);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Postal code validation
|
|
329
|
+
*/
|
|
330
|
+
function postalCode(value, country = 'US') {
|
|
331
|
+
if (typeof value !== 'string') return false;
|
|
332
|
+
|
|
333
|
+
const patterns = {
|
|
334
|
+
US: /^\d{5}(-\d{4})?$/,
|
|
335
|
+
UK: /^[A-Z]{1,2}\d{1,2}\s?\d[A-Z]{2}$/i,
|
|
336
|
+
CA: /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i,
|
|
337
|
+
DE: /^\d{5}$/,
|
|
338
|
+
FR: /^\d{5}$/,
|
|
339
|
+
JP: /^\d{3}-?\d{4}$/,
|
|
340
|
+
CN: /^\d{6}$/
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const pattern = patterns[country.toUpperCase()];
|
|
344
|
+
return pattern ? pattern.test(value) : false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* JSON validation
|
|
349
|
+
*/
|
|
350
|
+
function json(value) {
|
|
351
|
+
if (typeof value !== 'string') return false;
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
JSON.parse(value);
|
|
355
|
+
return true;
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Base64 validation
|
|
363
|
+
*/
|
|
364
|
+
function base64(value) {
|
|
365
|
+
if (typeof value !== 'string') return false;
|
|
366
|
+
return /^[A-Za-z0-9+/]*={0,2}$/.test(value);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Hex color validation
|
|
371
|
+
*/
|
|
372
|
+
function hexColor(value) {
|
|
373
|
+
if (typeof value !== 'string') return false;
|
|
374
|
+
return /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i.test(value);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* MAC address validation
|
|
379
|
+
*/
|
|
380
|
+
function macAddress(value) {
|
|
381
|
+
if (typeof value !== 'string') return false;
|
|
382
|
+
return /^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$/i.test(value);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = {
|
|
386
|
+
object,
|
|
387
|
+
array,
|
|
388
|
+
email,
|
|
389
|
+
url,
|
|
390
|
+
string,
|
|
391
|
+
number,
|
|
392
|
+
uuid,
|
|
393
|
+
ip,
|
|
394
|
+
creditCard,
|
|
395
|
+
dateRange,
|
|
396
|
+
phone,
|
|
397
|
+
postalCode,
|
|
398
|
+
json,
|
|
399
|
+
base64,
|
|
400
|
+
hexColor,
|
|
401
|
+
macAddress,
|
|
402
|
+
TYPE_VALIDATORS
|
|
403
|
+
};
|