@oscarpalmer/jhunal 0.21.0 → 0.23.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.
Files changed (47) hide show
  1. package/dist/constants.d.mts +7 -3
  2. package/dist/constants.mjs +34 -12
  3. package/dist/helpers/message.helper.d.mts +11 -0
  4. package/dist/helpers/message.helper.mjs +70 -0
  5. package/dist/helpers/misc.helper.d.mts +22 -0
  6. package/dist/helpers/misc.helper.mjs +56 -0
  7. package/dist/index.d.mts +318 -328
  8. package/dist/index.mjs +324 -295
  9. package/dist/models/schema.plain.model.d.mts +2 -8
  10. package/dist/models/validation.model.d.mts +26 -59
  11. package/dist/schematic.d.mts +28 -10
  12. package/dist/schematic.mjs +15 -16
  13. package/dist/validator/base.validator.d.mts +6 -0
  14. package/dist/validator/base.validator.mjs +19 -0
  15. package/dist/validator/function.validator.d.mts +6 -0
  16. package/dist/validator/function.validator.mjs +9 -0
  17. package/dist/validator/named.handler.d.mts +6 -0
  18. package/dist/validator/named.handler.mjs +22 -0
  19. package/dist/validator/named.validator.d.mts +7 -0
  20. package/dist/validator/named.validator.mjs +39 -0
  21. package/dist/validator/object.validator.d.mts +7 -0
  22. package/dist/validator/object.validator.mjs +167 -0
  23. package/dist/validator/schematic.validator.d.mts +7 -0
  24. package/dist/validator/schematic.validator.mjs +16 -0
  25. package/package.json +1 -1
  26. package/src/constants.ts +42 -10
  27. package/src/helpers/message.helper.ts +152 -0
  28. package/src/helpers/misc.helper.ts +93 -0
  29. package/src/index.ts +7 -2
  30. package/src/models/schema.plain.model.ts +1 -8
  31. package/src/models/validation.model.ts +55 -77
  32. package/src/schematic.ts +49 -27
  33. package/src/validator/base.validator.ts +31 -0
  34. package/src/validator/function.validator.ts +9 -0
  35. package/src/validator/named.handler.ts +50 -0
  36. package/src/validator/named.validator.ts +62 -0
  37. package/src/validator/object.validator.ts +340 -0
  38. package/src/validator/schematic.validator.ts +25 -0
  39. package/dist/helpers.d.mts +0 -28
  40. package/dist/helpers.mjs +0 -119
  41. package/dist/validation/property.validation.d.mts +0 -7
  42. package/dist/validation/property.validation.mjs +0 -92
  43. package/dist/validation/value.validation.d.mts +0 -7
  44. package/dist/validation/value.validation.mjs +0 -162
  45. package/src/helpers.ts +0 -246
  46. package/src/validation/property.validation.ts +0 -217
  47. package/src/validation/value.validation.ts +0 -293
package/src/constants.ts CHANGED
@@ -25,6 +25,8 @@ export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
25
25
 
26
26
  export const NAME_SCHEMATIC = 'Schematic';
27
27
 
28
+ export const NAME_SCHEMATIC_PREFIXED = 'a Schematic';
29
+
28
30
  export const NAME_ERROR_SCHEMATIC = 'SchematicError';
29
31
 
30
32
  export const NAME_ERROR_VALIDATION = 'ValidationError';
@@ -45,7 +47,7 @@ export const PROPERTY_VALIDATORS = '$validators';
45
47
 
46
48
  // #region Property validation
47
49
 
48
- export const VALIDATION_MESSAGE_INVALID_INPUT = "Expected 'object' as input but received <>";
50
+ export const VALIDATION_MESSAGE_INVALID_INPUT = 'Expected an object as input but received <>';
49
51
 
50
52
  export const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '<>'";
51
53
 
@@ -116,24 +118,54 @@ export const TEMPLATE_PATTERN = '<>';
116
118
 
117
119
  export const TYPE_ARRAY = 'array';
118
120
 
121
+ const TYPE_BIGINT = 'bigint';
122
+
123
+ const TYPE_BOOLEAN = 'boolean';
124
+
125
+ const TYPE_DATE = 'date';
126
+
127
+ export const TYPE_FUNCTION = 'function';
128
+
129
+ export const TYPE_FUNCTION_RESULT = 'a validated value';
130
+
119
131
  export const TYPE_NULL = 'null';
120
132
 
133
+ const TYPE_NUMBER = 'number';
134
+
121
135
  export const TYPE_OBJECT = 'object';
122
136
 
137
+ const TYPE_STRING = 'string';
138
+
139
+ const TYPE_SYMBOL = 'symbol';
140
+
123
141
  export const TYPE_UNDEFINED = 'undefined';
124
142
 
125
143
  export const VALIDATABLE_TYPES = new Set<ValueName>([
126
- 'array',
127
- 'bigint',
128
- 'boolean',
129
- 'date',
130
- 'function',
131
- 'number',
132
- 'string',
133
- 'symbol',
144
+ TYPE_ARRAY,
145
+ TYPE_BIGINT,
146
+ TYPE_BOOLEAN,
147
+ TYPE_DATE,
148
+ TYPE_FUNCTION,
149
+ TYPE_NUMBER,
134
150
  TYPE_OBJECT,
151
+ TYPE_STRING,
152
+ TYPE_SYMBOL,
135
153
  ]);
136
154
 
137
- export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
155
+ export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, TYPE_NULL, TYPE_UNDEFINED]);
156
+
157
+ export const PREFIXED_TYPES: Record<ValueName, string> = {
158
+ [TYPE_ARRAY]: `an ${TYPE_ARRAY}`,
159
+ [TYPE_BIGINT]: `a ${TYPE_BIGINT}`,
160
+ [TYPE_BOOLEAN]: `a ${TYPE_BOOLEAN}`,
161
+ [TYPE_DATE]: `a ${TYPE_DATE}`,
162
+ [TYPE_FUNCTION]: `a ${TYPE_FUNCTION}`,
163
+ [TYPE_NULL]: TYPE_NULL,
164
+ [TYPE_NUMBER]: `a ${TYPE_NUMBER}`,
165
+ [TYPE_STRING]: `a ${TYPE_STRING}`,
166
+ [TYPE_SYMBOL]: `a ${TYPE_SYMBOL}`,
167
+ [TYPE_OBJECT]: `an ${TYPE_OBJECT}`,
168
+ [TYPE_UNDEFINED]: TYPE_UNDEFINED,
169
+ };
138
170
 
139
171
  // #endregion
@@ -0,0 +1,152 @@
1
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import {
3
+ COMMA,
4
+ CONJUNCTION_AND,
5
+ CONJUNCTION_AND_COMMA,
6
+ CONJUNCTION_OR,
7
+ CONJUNCTION_OR_COMMA,
8
+ PREFIXED_TYPES,
9
+ TEMPLATE_PATTERN,
10
+ TYPE_ALL,
11
+ TYPE_ARRAY,
12
+ TYPE_FUNCTION,
13
+ TYPE_FUNCTION_RESULT,
14
+ TYPE_NULL,
15
+ TYPE_OBJECT,
16
+ VALIDATION_MESSAGE_INVALID_INPUT,
17
+ VALIDATION_MESSAGE_INVALID_REQUIRED,
18
+ VALIDATION_MESSAGE_INVALID_TYPE,
19
+ VALIDATION_MESSAGE_INVALID_VALUE,
20
+ VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX,
21
+ VALIDATION_MESSAGE_UNKNOWN_KEYS,
22
+ } from '../constants';
23
+ import type {ValueName} from '../models/misc.model';
24
+ import type {ValidatorType} from '../models/validation.model';
25
+
26
+ export function getInvalidInputMessage(actual: unknown): string {
27
+ return VALIDATION_MESSAGE_INVALID_INPUT.replace(TEMPLATE_PATTERN, getValueType(actual));
28
+ }
29
+
30
+ export function getInvalidMissingMessage(key: string, types: ValidatorType[]): string {
31
+ let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace(TEMPLATE_PATTERN, renderTypes(types));
32
+
33
+ message = message.replace(TEMPLATE_PATTERN, key);
34
+
35
+ return message;
36
+ }
37
+
38
+ export function getInvalidTypeMessage(
39
+ key: string,
40
+ types: ValidatorType[],
41
+ actual: unknown,
42
+ ): string {
43
+ let message = VALIDATION_MESSAGE_INVALID_TYPE.replace(TEMPLATE_PATTERN, renderTypes(types));
44
+
45
+ message = message.replace(TEMPLATE_PATTERN, key);
46
+ message = message.replace(TEMPLATE_PATTERN, getValueType(actual));
47
+
48
+ return message;
49
+ }
50
+
51
+ export function getInvalidValidatorMessage(
52
+ key: string,
53
+ type: ValueName,
54
+ index: number,
55
+ length: number,
56
+ ): string {
57
+ let message = VALIDATION_MESSAGE_INVALID_VALUE.replace(TEMPLATE_PATTERN, key);
58
+
59
+ message = message.replace(TEMPLATE_PATTERN, type);
60
+
61
+ if (length > 1) {
62
+ message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace(TEMPLATE_PATTERN, String(index));
63
+ }
64
+
65
+ return message;
66
+ }
67
+
68
+ function getPropertyType(type: ValidatorType): string {
69
+ switch (true) {
70
+ case typeof type === 'function':
71
+ return isConstructor(type) ? type.name : TYPE_FUNCTION_RESULT;
72
+
73
+ case TYPE_ALL.has(type as ValueName):
74
+ return PREFIXED_TYPES[type as ValueName];
75
+
76
+ default:
77
+ return PREFIXED_TYPES[TYPE_OBJECT];
78
+ }
79
+ }
80
+
81
+ export function getUnknownKeysMessage(keys: string[]): string {
82
+ return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace(TEMPLATE_PATTERN, renderKeys(keys));
83
+ }
84
+
85
+ function getValueType(value: unknown): string {
86
+ const valueType = typeof value;
87
+
88
+ switch (true) {
89
+ case value === null:
90
+ return TYPE_NULL;
91
+
92
+ case Array.isArray(value):
93
+ return PREFIXED_TYPES[TYPE_ARRAY];
94
+
95
+ case isPlainObject(value):
96
+ return PREFIXED_TYPES[TYPE_OBJECT];
97
+
98
+ case valueType !== TYPE_OBJECT:
99
+ return PREFIXED_TYPES[valueType as ValueName];
100
+
101
+ default:
102
+ return (value as object).constructor.name;
103
+ }
104
+ }
105
+
106
+ function renderKeys(keys: string[]): string {
107
+ return renderParts(
108
+ keys.map(key => `'${key}'`),
109
+ CONJUNCTION_AND,
110
+ CONJUNCTION_AND_COMMA,
111
+ );
112
+ }
113
+
114
+ function renderParts(parts: string[], delimiterShort: string, delimiterLong: string): string {
115
+ const {length} = parts;
116
+
117
+ if (length === 1) {
118
+ return parts[0];
119
+ }
120
+
121
+ let rendered = '';
122
+
123
+ for (let index = 0; index < length; index += 1) {
124
+ rendered += parts[index];
125
+
126
+ if (index < length - 2) {
127
+ rendered += COMMA;
128
+ } else if (index === length - 2) {
129
+ rendered += parts.length > 2 ? delimiterLong : delimiterShort;
130
+ }
131
+ }
132
+
133
+ return rendered;
134
+ }
135
+
136
+ function renderTypes(types: ValidatorType[]): string {
137
+ const unique = new Set<string>();
138
+ const parts: string[] = [];
139
+
140
+ for (let index = 0; index < types.length; index += 1) {
141
+ const rendered = getPropertyType(types[index]);
142
+
143
+ if (unique.has(rendered)) {
144
+ continue;
145
+ }
146
+
147
+ unique.add(rendered);
148
+ parts.push(rendered);
149
+ }
150
+
151
+ return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
152
+ }
@@ -0,0 +1,93 @@
1
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {Constructor, PlainObject} from '@oscarpalmer/atoms/models';
3
+ import {
4
+ MESSAGE_CONSTRUCTOR,
5
+ PROPERTY_SCHEMATIC,
6
+ REPORTING_ALL,
7
+ REPORTING_FIRST,
8
+ REPORTING_NONE,
9
+ REPORTING_THROW,
10
+ REPORTING_TYPES,
11
+ TYPE_OBJECT,
12
+ } from '../constants';
13
+ import type {
14
+ ReportingInformation,
15
+ ReportingType,
16
+ ValidatorParameters,
17
+ } from '../models/validation.model';
18
+ import type {Schematic} from '../schematic';
19
+
20
+ export function getParameters(input?: unknown): ValidatorParameters {
21
+ if (typeof input === 'boolean') {
22
+ return {
23
+ clone: true,
24
+ output: {},
25
+ reporting: getReporting(REPORTING_NONE),
26
+ strict: input,
27
+ };
28
+ }
29
+
30
+ if (REPORTING_TYPES.has(input as ReportingType)) {
31
+ return {
32
+ clone: true,
33
+ output: {},
34
+ reporting: getReporting(input as ReportingType),
35
+ strict: false,
36
+ };
37
+ }
38
+
39
+ const options = isPlainObject(input) ? input : {};
40
+
41
+ return {
42
+ clone: typeof options.clone === 'boolean' ? options.clone : true,
43
+ output: {},
44
+ reporting: getReporting(options.errors),
45
+ strict: typeof options.strict === 'boolean' ? options.strict : false,
46
+ };
47
+ }
48
+
49
+ export function getReporting(value: unknown): ReportingInformation {
50
+ const type = REPORTING_TYPES.has(value as ReportingType)
51
+ ? (value as ReportingType)
52
+ : REPORTING_NONE;
53
+
54
+ return {
55
+ type,
56
+ [REPORTING_ALL]: type === REPORTING_ALL,
57
+ [REPORTING_FIRST]: type === REPORTING_FIRST,
58
+ [REPORTING_NONE]: type === REPORTING_NONE,
59
+ [REPORTING_THROW]: type === REPORTING_THROW,
60
+ } as ReportingInformation;
61
+ }
62
+
63
+ /**
64
+ * Creates a validator function for a given constructor
65
+ * @param constructor - Constructor to check against
66
+ * @throws Will throw a `TypeError` if the provided argument is not a valid constructor
67
+ * @returns Validator function that checks if a value is an instance of the constructor
68
+ */
69
+ export function instanceOf<Instance>(
70
+ constructor: Constructor<Instance>,
71
+ ): (value: unknown) => value is Instance {
72
+ if (!isConstructor(constructor)) {
73
+ throw new TypeError(MESSAGE_CONSTRUCTOR);
74
+ }
75
+
76
+ return (value: unknown): value is Instance => {
77
+ return value instanceof constructor;
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Is the value a schematic?
83
+ * @param value Value to check
84
+ * @returns `true` if the value is a schematic, `false` otherwise
85
+ */
86
+ export function isSchematic(value: unknown): value is Schematic<never> {
87
+ return (
88
+ typeof value === TYPE_OBJECT &&
89
+ value !== null &&
90
+ PROPERTY_SCHEMATIC in (value as PlainObject) &&
91
+ (value as PlainObject)[PROPERTY_SCHEMATIC] === true
92
+ );
93
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,10 @@
1
- export {instanceOf, isSchematic} from './helpers';
1
+ export {instanceOf, isSchematic} from './helpers/misc.helper';
2
2
  export type {Schema} from './models/schema.plain.model';
3
3
  export type {TypedSchema} from './models/schema.typed.model';
4
- export {SchematicError, ValidationError} from './models/validation.model';
4
+ export {
5
+ SchematicError,
6
+ ValidationError,
7
+ type GetOptions,
8
+ type IsOptions,
9
+ } from './models/validation.model';
5
10
  export {schematic, type Schematic} from './schematic';
@@ -25,7 +25,7 @@ export type PlainSchema = {
25
25
  * };
26
26
  * ```
27
27
  */
28
- export type Schema = SchemaIndex;
28
+ export type Schema = PlainSchema;
29
29
 
30
30
  /**
31
31
  * A union of all valid types for a single schema entry
@@ -40,13 +40,6 @@ export type SchemaEntry =
40
40
  | ValueName
41
41
  | ((value: unknown) => boolean);
42
42
 
43
- /**
44
- * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link PlainSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
45
- */
46
- export interface SchemaIndex {
47
- [key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
48
- }
49
-
50
43
  /**
51
44
  * A property definition with explicit type(s), an optional requirement flag, and optional validators
52
45
  *
@@ -4,11 +4,18 @@ import {NAME_ERROR_SCHEMATIC, NAME_ERROR_VALIDATION} from '../constants';
4
4
  import type {Schematic} from '../schematic';
5
5
  import type {ValueName} from './misc.model';
6
6
 
7
+ // #region Named validation
8
+
9
+ export type NamedValidatorHandlers = {
10
+ [Key in ValueName]?: Array<(value: unknown) => boolean>;
11
+ };
12
+
13
+ export type NamedValidators = Record<ValueName, (value: unknown) => boolean>;
14
+
15
+ // #endregion
16
+
7
17
  // #region Reporting
8
18
 
9
- /**
10
- * Maps each {@link ReportingType} to a boolean flag
11
- */
12
19
  export type ReportingInformation = Record<ReportingType, boolean> & {
13
20
  type: ReportingType;
14
21
  };
@@ -16,16 +23,16 @@ export type ReportingInformation = Record<ReportingType, boolean> & {
16
23
  /**
17
24
  * Controls how validation failures are reported
18
25
  *
19
- * - `'none'` returns a boolean _(default)_
20
- * - `'first'` returns the first failure as a `Result`
21
- * - `'all'` returns all failures as a `Result` _(from same level)_
22
- * - `'throw'` throws a {@link ValidationError} on failure
26
+ * - `'none'`, returns a boolean _(default)_
27
+ * - `'first'`, returns the first failure as a `Result`
28
+ * - `'all'`, returns all failures as a `Result` _(from same level)_
29
+ * - `'throw'`, throws a {@link ValidationError} on failure
23
30
  */
24
31
  export type ReportingType = 'all' | 'first' | 'none' | 'throw';
25
32
 
26
33
  // #endregion
27
34
 
28
- // #region Schematic validation
35
+ // #region Errors
29
36
 
30
37
  /**
31
38
  * Thrown when a schema definition is invalid
@@ -38,66 +45,6 @@ export class SchematicError extends Error {
38
45
  }
39
46
  }
40
47
 
41
- // #endregion
42
-
43
- // #region Validated property
44
-
45
- /**
46
- * The runtime representation of a parsed schema property, used internally during validation
47
- *
48
- * @example
49
- * ```ts
50
- * const parsed: ValidatedProperty = {
51
- * key: 'age',
52
- * required: true,
53
- * types: ['number'],
54
- * validators: { number: [(v) => v > 0] },
55
- * };
56
- * ```
57
- */
58
- export type ValidatedProperty = {
59
- /**
60
- * The property name in the schema
61
- */
62
- key: string;
63
- /**
64
- * Whether the property is required
65
- */
66
- required: boolean;
67
- /**
68
- * The allowed types for this property
69
- */
70
- types: ValidatedPropertyType[];
71
- /**
72
- * Custom validators grouped by {@link ValueName}
73
- */
74
- validators: ValidatedPropertyValidators;
75
- };
76
-
77
- /**
78
- * A union of valid types for a {@link ValidatedProperty}'s `types` array
79
- *
80
- * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
81
- */
82
- export type ValidatedPropertyType =
83
- | GenericCallback
84
- | ValidatedProperty[]
85
- | Schematic<unknown>
86
- | ValueName;
87
-
88
- /**
89
- * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
90
- *
91
- * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
92
- */
93
- export type ValidatedPropertyValidators = {
94
- [Key in ValueName]?: Array<(value: unknown) => boolean>;
95
- };
96
-
97
- // #endregion
98
-
99
- // #region Property validation
100
-
101
48
  /**
102
49
  * Thrown in `'throw'` mode when one or more properties fail validation; `information` holds all failures
103
50
  */
@@ -114,6 +61,10 @@ export class ValidationError extends Error {
114
61
  }
115
62
  }
116
63
 
64
+ // #endregion
65
+
66
+ // #region Results
67
+
117
68
  /**
118
69
  * Describes a single validation failure
119
70
  */
@@ -129,34 +80,61 @@ export type ValidationInformation = {
129
80
  };
130
81
 
131
82
  /**
132
- *
83
+ *
133
84
  */
134
85
  export type ValidationInformationKey = {
135
86
  full: string;
136
87
  short: string;
137
88
  };
138
89
 
139
- /**
140
- * Options for validation
141
- */
142
- export type ValidationOptions<Errors extends ReportingType> = {
90
+ // #endregion
91
+
92
+ // #region Options
93
+
94
+ type BaseOptions<Errors extends ReportingType> = {
143
95
  /**
144
96
  * How should validation failures be reported; see {@link ReportingType} _(defaults to `'none'`)_
145
97
  */
146
- errors?: Errors;
98
+ errors: Errors;
147
99
  /**
148
100
  * Validate if unknown keys are present in the object? _(defaults to `false`)_
149
101
  */
150
102
  strict?: boolean;
151
103
  };
152
104
 
153
- export type ValidationParameters = {
105
+ /**
106
+ * Options for validating and getting a value from an input
107
+ */
108
+ export type GetOptions<Errors extends ReportingType> = BaseOptions<Errors> & {
109
+ /**
110
+ * Get a deeply cloned version of the input? _(defaults to `true`)_
111
+ */
112
+ clone?: boolean;
113
+ };
114
+
115
+ /**
116
+ * Options for validation an input value
117
+ */
118
+ export type IsOptions<Errors extends ReportingType> = BaseOptions<Errors>;
119
+
120
+ // #endregion
121
+
122
+ // #region Validator
123
+
124
+ export type Validator = (
125
+ input: unknown,
126
+ parameters: ValidatorParameters,
127
+ get: boolean,
128
+ ) => true | ValidationInformation[];
129
+
130
+ export type ValidatorParameters = {
131
+ clone: boolean;
154
132
  information?: ValidationInformation[];
155
- origin?: ValidatedProperty;
156
133
  output: PlainObject;
157
- prefix?: string;
158
134
  reporting: ReportingInformation;
159
135
  strict: boolean;
160
136
  };
161
137
 
138
+ export type ValidatorType = Function | PlainObject | Schematic<unknown> | ValueName;
139
+
162
140
  // #endregion