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.
@@ -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
+ };