@satoshibits/functional 1.0.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.
Files changed (46) hide show
  1. package/README.md +242 -0
  2. package/dist/array-utils.d.mts +317 -0
  3. package/dist/array-utils.d.mts.map +1 -0
  4. package/dist/array-utils.mjs +370 -0
  5. package/dist/array-utils.mjs.map +1 -0
  6. package/dist/composition.d.mts +603 -0
  7. package/dist/composition.d.mts.map +1 -0
  8. package/dist/composition.mjs +516 -0
  9. package/dist/composition.mjs.map +1 -0
  10. package/dist/object-utils.d.mts +267 -0
  11. package/dist/object-utils.d.mts.map +1 -0
  12. package/dist/object-utils.mjs +258 -0
  13. package/dist/object-utils.mjs.map +1 -0
  14. package/dist/option.d.mts +622 -0
  15. package/dist/option.d.mts.map +1 -0
  16. package/dist/option.mjs +637 -0
  17. package/dist/option.mjs.map +1 -0
  18. package/dist/performance.d.mts +265 -0
  19. package/dist/performance.d.mts.map +1 -0
  20. package/dist/performance.mjs +453 -0
  21. package/dist/performance.mjs.map +1 -0
  22. package/dist/pipeline.d.mts +431 -0
  23. package/dist/pipeline.d.mts.map +1 -0
  24. package/dist/pipeline.mjs +460 -0
  25. package/dist/pipeline.mjs.map +1 -0
  26. package/dist/predicates.d.mts +722 -0
  27. package/dist/predicates.d.mts.map +1 -0
  28. package/dist/predicates.mjs +802 -0
  29. package/dist/predicates.mjs.map +1 -0
  30. package/dist/reader-result.d.mts +422 -0
  31. package/dist/reader-result.d.mts.map +1 -0
  32. package/dist/reader-result.mjs +758 -0
  33. package/dist/reader-result.mjs.map +1 -0
  34. package/dist/result.d.mts +684 -0
  35. package/dist/result.d.mts.map +1 -0
  36. package/dist/result.mjs +814 -0
  37. package/dist/result.mjs.map +1 -0
  38. package/dist/types.d.mts +439 -0
  39. package/dist/types.d.mts.map +1 -0
  40. package/dist/types.mjs +191 -0
  41. package/dist/types.mjs.map +1 -0
  42. package/dist/validation.d.mts +622 -0
  43. package/dist/validation.d.mts.map +1 -0
  44. package/dist/validation.mjs +852 -0
  45. package/dist/validation.mjs.map +1 -0
  46. package/package.json +46 -0
@@ -0,0 +1,852 @@
1
+ "use strict";
2
+ /**
3
+ * @module validation
4
+ * @description Functional validation utilities using Result types for composable,
5
+ * type-safe data validation. This module provides a rich set of validators and
6
+ * combinators for building complex validation schemas. Unlike traditional validation
7
+ * libraries that throw exceptions, all validators return Result types, making
8
+ * error handling explicit and composable. Validators can be combined, transformed,
9
+ * and reused to build sophisticated validation logic.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Validation, validators, schema, ValidationError } from './validation.mts';
14
+ *
15
+ * // simple validators
16
+ * const validateAge = validators.number.between(0, 150);
17
+ * const validateEmail = validators.string.email();
18
+ *
19
+ * // combining validators
20
+ * const validatePassword = Validation.all(
21
+ * validators.string.minLength(8),
22
+ * validators.string.matches(/[A-Z]/, 'Must contain uppercase'),
23
+ * validators.string.matches(/[0-9]/, 'Must contain number')
24
+ * );
25
+ *
26
+ * // object validation schema
27
+ * const userSchema = schema({
28
+ * name: validators.string.nonEmpty(),
29
+ * email: validateEmail,
30
+ * age: Validation.optional(validateAge),
31
+ * password: validatePassword
32
+ * });
33
+ *
34
+ * // using the validator
35
+ * const result = userSchema({
36
+ * name: 'John Doe',
37
+ * email: 'john@example.com',
38
+ * age: 30,
39
+ * password: 'SecurePass123'
40
+ * });
41
+ *
42
+ * if (result.success) {
43
+ * console.log('Valid user:', result.data);
44
+ * } else {
45
+ * console.error('Validation errors:', result.error.errors);
46
+ * }
47
+ * ```
48
+ *
49
+ * @category Core
50
+ * @since 2025-07-03
51
+ */
52
+ var __extends = (this && this.__extends) || (function () {
53
+ var extendStatics = function (d, b) {
54
+ extendStatics = Object.setPrototypeOf ||
55
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
56
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
57
+ return extendStatics(d, b);
58
+ };
59
+ return function (d, b) {
60
+ if (typeof b !== "function" && b !== null)
61
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
62
+ extendStatics(d, b);
63
+ function __() { this.constructor = d; }
64
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
65
+ };
66
+ })();
67
+ var __assign = (this && this.__assign) || function () {
68
+ __assign = Object.assign || function(t) {
69
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
70
+ s = arguments[i];
71
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
72
+ t[p] = s[p];
73
+ }
74
+ return t;
75
+ };
76
+ return __assign.apply(this, arguments);
77
+ };
78
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
79
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
80
+ if (ar || !(i in from)) {
81
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
82
+ ar[i] = from[i];
83
+ }
84
+ }
85
+ return to.concat(ar || Array.prototype.slice.call(from));
86
+ };
87
+ import { Result } from "./result.mjs";
88
+ /**
89
+ * Custom validation error that can hold multiple error messages.
90
+ * @description Extends the standard Error class to accumulate multiple validation
91
+ * errors. This allows validators to collect all errors rather than failing on
92
+ * the first error, providing better user experience for form validation.
93
+ *
94
+ * @category Errors
95
+ * @example
96
+ * // Creating validation errors
97
+ * const error = new ValidationError(['Name is required', 'Email is invalid']);
98
+ * console.log(error.message); // "Validation failed: Name is required, Email is invalid"
99
+ *
100
+ * @example
101
+ * // Working with errors
102
+ * if (!result.success) {
103
+ * const validationError = result.error;
104
+ * console.log('First error:', validationError.firstError());
105
+ * console.log('All errors:', validationError.errors);
106
+ *
107
+ * if (validationError.hasError('Email is invalid')) {
108
+ * // Handle email error specifically
109
+ * }
110
+ * }
111
+ *
112
+ * @since 2025-07-03
113
+ */
114
+ var ValidationError = /** @class */ (function (_super) {
115
+ __extends(ValidationError, _super);
116
+ /**
117
+ * Creates a new ValidationError with the given error messages.
118
+ *
119
+ * @param {string[]} errors - Array of error messages
120
+ */
121
+ function ValidationError(errors) {
122
+ var _this = _super.call(this, "Validation failed: ".concat(errors.join(", "))) || this;
123
+ _this.errors = errors;
124
+ _this.name = "ValidationError";
125
+ return _this;
126
+ }
127
+ /**
128
+ * Adds additional errors to this validation error.
129
+ * @description Creates a new ValidationError with the combined errors.
130
+ * The original error remains unchanged (immutable).
131
+ *
132
+ * @param {string[]} newErrors - Additional error messages to add
133
+ * @returns {ValidationError} A new ValidationError with all errors
134
+ *
135
+ * @example
136
+ * const error1 = new ValidationError(['Name required']);
137
+ * const error2 = error1.addErrors(['Email invalid']);
138
+ * // error2.errors => ['Name required', 'Email invalid']
139
+ * // error1.errors => ['Name required'] (unchanged)
140
+ */
141
+ ValidationError.prototype.addErrors = function (newErrors) {
142
+ return new ValidationError(__spreadArray(__spreadArray([], this.errors, true), newErrors, true));
143
+ };
144
+ /**
145
+ * Checks if this error contains a specific error message.
146
+ * @description Useful for conditional error handling based on specific
147
+ * validation failures.
148
+ *
149
+ * @param {string} error - The error message to check for
150
+ * @returns {boolean} True if the error message is present
151
+ *
152
+ * @example
153
+ * if (!result.success && result.error.hasError('Email is invalid')) {
154
+ * showEmailHelp();
155
+ * }
156
+ */
157
+ ValidationError.prototype.hasError = function (error) {
158
+ return this.errors.includes(error);
159
+ };
160
+ /**
161
+ * Gets the first error message.
162
+ * @description Returns the first error in the list, useful when you only
163
+ * want to display one error at a time.
164
+ *
165
+ * @returns {string | undefined} The first error message or undefined if no errors
166
+ *
167
+ * @example
168
+ * const firstError = validationError.firstError();
169
+ * if (firstError) {
170
+ * showToast(firstError);
171
+ * }
172
+ */
173
+ ValidationError.prototype.firstError = function () {
174
+ return this.errors[0];
175
+ };
176
+ return ValidationError;
177
+ }(Error));
178
+ export { ValidationError };
179
+ /**
180
+ * Validation utilities for creating and composing validators.
181
+ * @description The main namespace for validation combinators and utilities.
182
+ * Provides methods for creating, combining, and transforming validators
183
+ * in a functional style.
184
+ *
185
+ * @category Utilities
186
+ * @since 2025-07-03
187
+ */
188
+ export var Validation = {
189
+ /**
190
+ * Creates a validator that always succeeds.
191
+ * @description Useful as a default validator or when conditionally applying
192
+ * validation. The value passes through unchanged.
193
+ *
194
+ * @template T - The type of value
195
+ * @returns {Validator<T>} A validator that always returns success
196
+ *
197
+ * @category Constructors
198
+ * @example
199
+ * // Conditional validation
200
+ * const validator = shouldValidate
201
+ * ? validators.string.email()
202
+ * : Validation.success();
203
+ *
204
+ * @since 2025-07-03
205
+ */
206
+ success: function () {
207
+ return function (value) {
208
+ return Result.ok(value);
209
+ };
210
+ },
211
+ /**
212
+ * Creates a validator that always fails with the given error.
213
+ * @description Useful for custom validation logic or placeholder validators
214
+ * during development.
215
+ *
216
+ * @template T - The type of value
217
+ * @param {string} error - The error message
218
+ * @returns {Validator<T>} A validator that always returns failure
219
+ *
220
+ * @category Constructors
221
+ * @example
222
+ * // Feature flag validation
223
+ * const validator = featureEnabled
224
+ * ? actualValidator
225
+ * : Validation.failure('Feature not available');
226
+ *
227
+ * @since 2025-07-03
228
+ */
229
+ failure: function (error) {
230
+ return function () {
231
+ return Result.err(new ValidationError([error]));
232
+ };
233
+ },
234
+ /**
235
+ * Creates a validator from a predicate function.
236
+ * @description The fundamental building block for custom validators. Converts
237
+ * a boolean-returning function into a validator.
238
+ *
239
+ * @template T - The type of value to validate
240
+ * @param {function(T): boolean} predicate - Function that returns true if valid
241
+ * @param {string} error - Error message if validation fails
242
+ * @returns {Validator<T>} A validator based on the predicate
243
+ *
244
+ * @category Constructors
245
+ * @example
246
+ * // Custom age validator
247
+ * const isAdult = Validation.fromPredicate(
248
+ * (age: number) => age >= 18,
249
+ * 'Must be 18 or older'
250
+ * );
251
+ *
252
+ * @example
253
+ * // Complex validation
254
+ * const isValidUsername = Validation.fromPredicate(
255
+ * (username: string) => /^[a-zA-Z0-9_]{3,20}$/.test(username),
256
+ * 'Username must be 3-20 characters, alphanumeric or underscore'
257
+ * );
258
+ *
259
+ * @since 2025-07-03
260
+ */
261
+ fromPredicate: function (predicate, error) {
262
+ return function (value) {
263
+ return predicate(value)
264
+ ? Result.ok(value)
265
+ : Result.err(new ValidationError([error]));
266
+ };
267
+ },
268
+ /**
269
+ * Combines multiple validators using AND logic.
270
+ * @description All validators must pass for the validation to succeed.
271
+ * Collects all errors from all validators before returning, providing
272
+ * comprehensive feedback.
273
+ *
274
+ * @template T - The type of value to validate
275
+ * @param {Validator<T>[]} validators - Validators to combine
276
+ * @returns {Validator<T>} A validator that requires all validations to pass
277
+ *
278
+ * @category Combinators
279
+ * @example
280
+ * // Password validation
281
+ * const validatePassword = Validation.all(
282
+ * validators.string.minLength(8),
283
+ * validators.string.matches(/[A-Z]/, 'Must contain uppercase'),
284
+ * validators.string.matches(/[0-9]/, 'Must contain number'),
285
+ * validators.string.matches(/[!@#$%]/, 'Must contain special character')
286
+ * );
287
+ *
288
+ * @example
289
+ * // Numeric range validation
290
+ * const validatePercentage = Validation.all(
291
+ * validators.number.min(0),
292
+ * validators.number.max(100),
293
+ * validators.number.integer()
294
+ * );
295
+ *
296
+ * @since 2025-07-03
297
+ */
298
+ all: function () {
299
+ var validators = [];
300
+ for (var _i = 0; _i < arguments.length; _i++) {
301
+ validators[_i] = arguments[_i];
302
+ }
303
+ return function (value) {
304
+ var errors = [];
305
+ for (var _i = 0, validators_1 = validators; _i < validators_1.length; _i++) {
306
+ var validator = validators_1[_i];
307
+ var result = validator(value);
308
+ if (!result.success) {
309
+ errors.push.apply(errors, result.error.errors);
310
+ }
311
+ }
312
+ return errors.length === 0
313
+ ? Result.ok(value)
314
+ : Result.err(new ValidationError(errors));
315
+ };
316
+ },
317
+ /**
318
+ * Combines multiple validators using OR logic.
319
+ * @description At least one validator must pass for the validation to succeed.
320
+ * Returns the result of the first successful validator, or all errors if
321
+ * none succeed.
322
+ *
323
+ * @template T - The type of value to validate
324
+ * @param {Validator<T>[]} validators - Validators to try
325
+ * @returns {Validator<T>} A validator that requires at least one validation to pass
326
+ *
327
+ * @category Combinators
328
+ * @example
329
+ * // Multiple format support
330
+ * const validateDate = Validation.any(
331
+ * validators.string.matches(/^\d{4}-\d{2}-\d{2}$/, 'Invalid ISO date'),
332
+ * validators.string.matches(/^\d{2}\/\d{2}\/\d{4}$/, 'Invalid US date')
333
+ * );
334
+ *
335
+ * @example
336
+ * // Flexible identifier
337
+ * const validateIdentifier = Validation.any(
338
+ * validators.string.matches(/^\d+$/, 'Not a numeric ID'),
339
+ * validators.string.email(),
340
+ * validators.string.matches(/^[A-Z]{2,}$/, 'Not a code')
341
+ * );
342
+ *
343
+ * @since 2025-07-03
344
+ */
345
+ any: function () {
346
+ var validators = [];
347
+ for (var _i = 0; _i < arguments.length; _i++) {
348
+ validators[_i] = arguments[_i];
349
+ }
350
+ return function (value) {
351
+ var errors = [];
352
+ for (var _i = 0, validators_2 = validators; _i < validators_2.length; _i++) {
353
+ var validator = validators_2[_i];
354
+ var result = validator(value);
355
+ if (result.success) {
356
+ return result;
357
+ }
358
+ else {
359
+ errors.push.apply(errors, result.error.errors);
360
+ }
361
+ }
362
+ return Result.err(new ValidationError(errors));
363
+ };
364
+ },
365
+ /**
366
+ * Transforms the validated value if validation passes.
367
+ * @description Note: The returned validator still takes an input of type T.
368
+ * This is the functor map operation for validators, allowing value transformation
369
+ * after successful validation.
370
+ *
371
+ * @template T - The input type
372
+ * @template U - The output type
373
+ * @param {function(T): U} fn - Function to transform the validated value
374
+ * @returns {function(Validator<T>): function(T): Result<U, ValidationError>} A function that transforms validators
375
+ *
376
+ * @category Transformations
377
+ * @example
378
+ * // Normalize email
379
+ * const normalizeEmail = Validation.map((email: string) => email.toLowerCase());
380
+ * const validateEmail = normalizeEmail(validators.string.email());
381
+ *
382
+ * @example
383
+ * // Parse and validate
384
+ * const parseNumber = Validation.map((s: string) => parseInt(s, 10));
385
+ * const validateNumericString = parseNumber(
386
+ * validators.string.matches(/^\d+$/, 'Must be numeric')
387
+ * );
388
+ *
389
+ * @since 2025-07-03
390
+ */
391
+ map: function (fn) {
392
+ return function (validator) {
393
+ return function (value) {
394
+ var result = validator(value);
395
+ return result.success ? Result.ok(fn(result.data)) : result;
396
+ };
397
+ };
398
+ },
399
+ /**
400
+ * Chains validators together.
401
+ * @description The choice of the second validator depends on the successful
402
+ * result of the first. This is the monadic bind operation for validators,
403
+ * enabling dynamic validation based on previous results.
404
+ *
405
+ * @template T - The type being validated
406
+ * @param {function(T): Validator<T>} fn - Function that returns the next validator
407
+ * @returns {function(Validator<T>): Validator<T>} A function that chains validators
408
+ *
409
+ * @category Combinators
410
+ * @example
411
+ * // Conditional validation based on value
412
+ * const validateScore = Validation.flatMap((score: number) => {
413
+ * if (score < 0) return Validation.failure('Negative scores not allowed');
414
+ * if (score > 100) return validators.number.max(200); // Allow bonus points
415
+ * return Validation.success();
416
+ * });
417
+ *
418
+ * @example
419
+ * // Dynamic validation
420
+ * const validateField = Validation.flatMap((field: { type: string; value: any }) => {
421
+ * switch (field.type) {
422
+ * case 'email': return validators.string.email();
423
+ * case 'number': return validators.number.positive();
424
+ * default: return Validation.success();
425
+ * }
426
+ * });
427
+ *
428
+ * @since 2025-07-03
429
+ */
430
+ flatMap: function (fn) {
431
+ return function (validator) {
432
+ return function (value) {
433
+ var result = validator(value);
434
+ if (result.success) {
435
+ // Get the next validator based on the validated data
436
+ var nextValidator = fn(result.data);
437
+ // Apply the next validator to the successfully validated data
438
+ return nextValidator(result.data);
439
+ }
440
+ // For error case, we return the original error
441
+ return result;
442
+ };
443
+ };
444
+ },
445
+ /**
446
+ * Validates an optional value.
447
+ * @description If the value is null or undefined, validation passes.
448
+ * Otherwise, applies the validator. Useful for optional form fields
449
+ * or nullable database columns.
450
+ *
451
+ * @template T - The type being validated
452
+ * @param {Validator<T>} validator - Validator to apply if value is present
453
+ * @returns {Validator<T | null | undefined>} A validator that handles optional values
454
+ *
455
+ * @category Modifiers
456
+ * @example
457
+ * // Optional email field
458
+ * const validateOptionalEmail = Validation.optional(
459
+ * validators.string.email()
460
+ * );
461
+ *
462
+ * validateOptionalEmail(null); // => { success: true, data: null }
463
+ * validateOptionalEmail('user@example.com'); // => validates as email
464
+ *
465
+ * @example
466
+ * // In object schema
467
+ * const userSchema = schema({
468
+ * name: validators.string.nonEmpty(),
469
+ * email: validators.string.email(),
470
+ * phone: Validation.optional(validators.string.matches(/^\d{10}$/))
471
+ * });
472
+ *
473
+ * @since 2025-07-03
474
+ */
475
+ optional: function (validator) {
476
+ return function (value) {
477
+ if (value === null || value === undefined) {
478
+ return Result.ok(value);
479
+ }
480
+ return validator(value);
481
+ };
482
+ },
483
+ /**
484
+ * Makes a validator required.
485
+ * @description Fails if value is null or undefined. This is a type guard
486
+ * that narrows T | null | undefined to T.
487
+ *
488
+ * @template T - The required type
489
+ * @param {string} error - Error message if value is missing
490
+ * @returns {Validator<T | null | undefined>} A validator that requires presence
491
+ *
492
+ * @category Modifiers
493
+ * @example
494
+ * // Basic usage
495
+ * const required = Validation.required<string>();
496
+ * required(null); // => { success: false, error: 'Value is required' }
497
+ * required('hello'); // => { success: true, data: 'hello' }
498
+ *
499
+ * @example
500
+ * // Combining with other validators
501
+ * const validateName = Validation.all(
502
+ * Validation.required<string>('Name is required'),
503
+ * validators.string.minLength(2)
504
+ * );
505
+ *
506
+ * @since 2025-07-03
507
+ */
508
+ required: function (error) {
509
+ if (error === void 0) { error = "Value is required"; }
510
+ return function (value) {
511
+ if (value === null || value === undefined) {
512
+ return Result.err(new ValidationError([error]));
513
+ }
514
+ return Result.ok(value);
515
+ };
516
+ },
517
+ /**
518
+ * Validates each item in an array.
519
+ * @description Applies the same validator to each element of an array,
520
+ * collecting all errors with their indices. Returns a new array with
521
+ * validated (and possibly transformed) items.
522
+ *
523
+ * @template T - The type of array elements
524
+ * @param {Validator<T>} itemValidator - Validator to apply to each item
525
+ * @returns {Validator<T[]>} A validator for arrays
526
+ *
527
+ * @category Collections
528
+ * @example
529
+ * // Validate array of emails
530
+ * const validateEmails = Validation.array(
531
+ * validators.string.email()
532
+ * );
533
+ *
534
+ * const result = validateEmails([
535
+ * 'user@example.com',
536
+ * 'invalid-email',
537
+ * 'admin@example.com'
538
+ * ]);
539
+ * // Error: "[1]: Invalid email format"
540
+ *
541
+ * @example
542
+ * // Complex item validation
543
+ * const validateUsers = Validation.array(
544
+ * schema({
545
+ * name: validators.string.nonEmpty(),
546
+ * age: validators.number.positive()
547
+ * })
548
+ * );
549
+ *
550
+ * @since 2025-07-03
551
+ */
552
+ array: function (itemValidator) {
553
+ return function (values) {
554
+ var errors = [];
555
+ var validatedItems = [];
556
+ values.forEach(function (value, index) {
557
+ var result = itemValidator(value);
558
+ if (result.success) {
559
+ validatedItems.push(result.data);
560
+ }
561
+ else {
562
+ errors.push.apply(errors, result.error.errors.map(function (err) { return "[".concat(index, "]: ").concat(err); }));
563
+ }
564
+ });
565
+ return errors.length === 0
566
+ ? Result.ok(validatedItems)
567
+ : Result.err(new ValidationError(errors));
568
+ };
569
+ },
570
+ /**
571
+ * Validates properties of an object.
572
+ * @description Validates specified properties of an object using individual
573
+ * validators. Unspecified properties are passed through unchanged. Errors
574
+ * are prefixed with the property name for clarity.
575
+ *
576
+ * @template T - The object type
577
+ * @param {object} validators - Map of property names to validators
578
+ * @returns {Validator<T>} A validator for the object
579
+ *
580
+ * @category Objects
581
+ * @example
582
+ * // Partial object validation
583
+ * const validatePerson = Validation.object({
584
+ * name: validators.string.nonEmpty(),
585
+ * age: validators.number.positive()
586
+ * });
587
+ *
588
+ * const result = validatePerson({
589
+ * name: 'John',
590
+ * age: -5,
591
+ * extra: 'ignored'
592
+ * });
593
+ * // Error: "age: Number must be positive"
594
+ *
595
+ * @see schema - Type-safe alternative for complete object validation
596
+ * @since 2025-07-03
597
+ */
598
+ object:
599
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Needed for flexible object validation
600
+ function (validators) {
601
+ return function (obj) {
602
+ var errors = [];
603
+ var validatedObj = null;
604
+ var _loop_1 = function (key, validator) {
605
+ if (validator) {
606
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type erasure from Object.entries
607
+ var result = validator(obj[key]);
608
+ if (!result.success) {
609
+ errors.push.apply(errors, result.error.errors.map(function (err) { return "".concat(key, ": ").concat(err); }));
610
+ }
611
+ else if (result.data !== obj[key]) {
612
+ // Lazily create a copy only if data is transformed
613
+ validatedObj !== null && validatedObj !== void 0 ? validatedObj : (validatedObj = __assign({}, obj));
614
+ validatedObj[key] = result.data;
615
+ }
616
+ }
617
+ };
618
+ for (var _i = 0, _a = Object.entries(validators); _i < _a.length; _i++) {
619
+ var _b = _a[_i], key = _b[0], validator = _b[1];
620
+ _loop_1(key, validator);
621
+ }
622
+ return errors.length === 0
623
+ ? Result.ok(validatedObj !== null && validatedObj !== void 0 ? validatedObj : obj)
624
+ : Result.err(new ValidationError(errors));
625
+ };
626
+ },
627
+ };
628
+ /**
629
+ * Common validators for primitive types.
630
+ * @description Pre-built validators for common validation scenarios.
631
+ * These validators can be used directly or combined with the Validation
632
+ * combinators to create more complex validation logic.
633
+ *
634
+ * @category Validators
635
+ * @example
636
+ * // Using validators directly
637
+ * const emailValidator = validators.string.email();
638
+ * const ageValidator = validators.number.between(0, 150);
639
+ *
640
+ * @example
641
+ * // Combining validators
642
+ * const strongPassword = Validation.all(
643
+ * validators.string.minLength(12),
644
+ * validators.string.matches(/[A-Z]/, 'Need uppercase'),
645
+ * validators.string.matches(/[a-z]/, 'Need lowercase'),
646
+ * validators.string.matches(/[0-9]/, 'Need number')
647
+ * );
648
+ *
649
+ * @since 2025-07-03
650
+ */
651
+ export var validators = {
652
+ /**
653
+ * String validators.
654
+ * @description Validators for string values including length checks,
655
+ * format validation, and pattern matching.
656
+ *
657
+ * @category String Validators
658
+ * @since 2025-07-03
659
+ */
660
+ string: {
661
+ minLength: function (min) {
662
+ return Validation.fromPredicate(function (str) { return str.length >= min; }, "String must be at least ".concat(min, " characters long"));
663
+ },
664
+ maxLength: function (max) {
665
+ return Validation.fromPredicate(function (str) { return str.length <= max; }, "String must be at most ".concat(max, " characters long"));
666
+ },
667
+ nonEmpty: function () {
668
+ return Validation.fromPredicate(function (str) { return str.trim().length > 0; }, "String cannot be empty");
669
+ },
670
+ email: function () {
671
+ return Validation.fromPredicate(
672
+ // A more permissive regex, see https://emailregex.com/
673
+ function (str) { return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(str); }, "Invalid email format");
674
+ },
675
+ url: function () {
676
+ return Validation.fromPredicate(function (str) {
677
+ try {
678
+ new URL(str);
679
+ return true;
680
+ }
681
+ catch (_a) {
682
+ return false;
683
+ }
684
+ }, "Invalid URL format");
685
+ },
686
+ matches: function (pattern, error) {
687
+ if (error === void 0) { error = "String does not match pattern"; }
688
+ return Validation.fromPredicate(function (str) { return pattern.test(str); }, error);
689
+ },
690
+ oneOf: function (options) {
691
+ return Validation.fromPredicate(function (str) { return options.includes(str); }, "String must be one of: ".concat(options.join(", ")));
692
+ },
693
+ },
694
+ /**
695
+ * Number validators.
696
+ */
697
+ number: {
698
+ min: function (min) {
699
+ return Validation.fromPredicate(function (num) { return num >= min; }, "Number must be at least ".concat(min));
700
+ },
701
+ max: function (max) {
702
+ return Validation.fromPredicate(function (num) { return num <= max; }, "Number must be at most ".concat(max));
703
+ },
704
+ positive: function () {
705
+ return Validation.fromPredicate(function (num) { return num > 0; }, "Number must be positive");
706
+ },
707
+ nonNegative: function () {
708
+ return Validation.fromPredicate(function (num) { return num >= 0; }, "Number must be non-negative");
709
+ },
710
+ integer: function () {
711
+ return Validation.fromPredicate(function (num) { return Number.isInteger(num); }, "Number must be an integer");
712
+ },
713
+ between: function (min, max) {
714
+ return Validation.fromPredicate(function (num) { return num >= min && num <= max; }, "Number must be between ".concat(min, " and ").concat(max));
715
+ },
716
+ },
717
+ /**
718
+ * Array validators.
719
+ */
720
+ array: {
721
+ minLength: function (min) {
722
+ return Validation.fromPredicate(function (arr) { return arr.length >= min; }, "Array must have at least ".concat(min, " items"));
723
+ },
724
+ maxLength: function (max) {
725
+ return Validation.fromPredicate(function (arr) { return arr.length <= max; }, "Array must have at most ".concat(max, " items"));
726
+ },
727
+ nonEmpty: function () {
728
+ return Validation.fromPredicate(function (arr) { return arr.length > 0; }, "Array cannot be empty");
729
+ },
730
+ unique: function () {
731
+ return Validation.fromPredicate(function (arr) { return new Set(arr).size === arr.length; }, "Array must contain unique items");
732
+ },
733
+ },
734
+ /**
735
+ * Date validators.
736
+ */
737
+ date: {
738
+ after: function (date) {
739
+ return Validation.fromPredicate(function (d) { return d > date; }, "Date must be after ".concat(date.toISOString()));
740
+ },
741
+ before: function (date) {
742
+ return Validation.fromPredicate(function (d) { return d < date; }, "Date must be before ".concat(date.toISOString()));
743
+ },
744
+ future: function () {
745
+ return Validation.fromPredicate(function (d) { return d > new Date(); }, "Date must be in the future");
746
+ },
747
+ past: function () {
748
+ return Validation.fromPredicate(function (d) { return d < new Date(); }, "Date must be in the past");
749
+ },
750
+ },
751
+ /**
752
+ * Object validators.
753
+ */
754
+ object: {
755
+ hasProperty: function (prop) {
756
+ return Validation.fromPredicate(function (obj) { return prop in obj; }, "Object must have property '".concat(String(prop), "'"));
757
+ },
758
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Flexible for any object type
759
+ notEmpty: function () {
760
+ return Validation.fromPredicate(
761
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Matches return type
762
+ function (obj) { return Object.keys(obj).length > 0; }, "Object cannot be empty");
763
+ },
764
+ },
765
+ };
766
+ /**
767
+ * Utility for creating complex validation schemas.
768
+ * @description Type-safe wrapper around Validation.object that requires
769
+ * validators for all properties of the type. This ensures complete
770
+ * validation coverage for object types.
771
+ *
772
+ * @template T - The object type to validate
773
+ * @param {object} validators - Validators for each property of T
774
+ * @returns {Validator<T>} A validator for objects of type T
775
+ *
776
+ * @category Schema
777
+ * @example
778
+ * // Type-safe user validation
779
+ * interface User {
780
+ * name: string;
781
+ * email: string;
782
+ * age: number;
783
+ * }
784
+ *
785
+ * const userValidator = schema<User>({
786
+ * name: validators.string.nonEmpty(),
787
+ * email: validators.string.email(),
788
+ * age: validators.number.between(0, 150)
789
+ * });
790
+ *
791
+ * @example
792
+ * // Nested schemas
793
+ * const addressValidator = schema({
794
+ * street: validators.string.nonEmpty(),
795
+ * city: validators.string.nonEmpty(),
796
+ * zipCode: validators.string.matches(/^\d{5}$/)
797
+ * });
798
+ *
799
+ * const personValidator = schema({
800
+ * name: validators.string.nonEmpty(),
801
+ * address: addressValidator
802
+ * });
803
+ *
804
+ * @since 2025-07-03
805
+ */
806
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Needed for flexible object validation
807
+ export var schema = function (validators) { return Validation.object(validators); };
808
+ /**
809
+ * Validates a value and returns either the validated value or throws the error.
810
+ * @description Use sparingly, only when you're certain validation should pass.
811
+ * This bridges the Result-based validation system with exception-based code.
812
+ *
813
+ * @template T - The type being validated
814
+ * @param {Validator<T>} validator - The validator to apply
815
+ * @returns {function(T): T} A function that validates or throws
816
+ * @throws {ValidationError} If validation fails
817
+ *
818
+ * @category Utilities
819
+ * @example
820
+ * // Configuration validation at startup
821
+ * const validateConfig = validateOrThrow(
822
+ * schema({
823
+ * port: validators.number.between(1, 65535),
824
+ * host: validators.string.nonEmpty()
825
+ * })
826
+ * );
827
+ *
828
+ * // Throws if invalid, otherwise returns validated config
829
+ * const config = validateConfig(loadConfig());
830
+ *
831
+ * @example
832
+ * // Input sanitization
833
+ * const sanitizeEmail = validateOrThrow(
834
+ * Validation.map((s: string) => s.toLowerCase())(
835
+ * validators.string.email()
836
+ * )
837
+ * );
838
+ *
839
+ * @since 2025-07-03
840
+ */
841
+ export var validateOrThrow = function (validator) {
842
+ return function (value) {
843
+ var result = validator(value);
844
+ if (result.success) {
845
+ return result.data;
846
+ }
847
+ else {
848
+ throw result.error;
849
+ }
850
+ };
851
+ };
852
+ //# sourceMappingURL=validation.mjs.map