@oscarpalmer/jhunal 0.16.0 → 0.18.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 (40) hide show
  1. package/dist/constants.d.mts +28 -15
  2. package/dist/constants.mjs +31 -14
  3. package/dist/helpers.d.mts +8 -1
  4. package/dist/helpers.mjs +68 -3
  5. package/dist/index.d.mts +283 -262
  6. package/dist/index.mjs +189 -56
  7. package/dist/models/infer.model.d.mts +66 -0
  8. package/dist/models/infer.model.mjs +1 -0
  9. package/dist/models/misc.model.d.mts +153 -0
  10. package/dist/models/misc.model.mjs +1 -0
  11. package/dist/models/schema.plain.model.d.mts +92 -0
  12. package/dist/models/schema.plain.model.mjs +1 -0
  13. package/dist/models/schema.typed.model.d.mts +96 -0
  14. package/dist/models/schema.typed.model.mjs +1 -0
  15. package/dist/models/transform.model.d.mts +59 -0
  16. package/dist/models/transform.model.mjs +1 -0
  17. package/dist/models/validation.model.d.mts +81 -0
  18. package/dist/models/validation.model.mjs +21 -0
  19. package/dist/schematic.d.mts +20 -6
  20. package/dist/schematic.mjs +7 -12
  21. package/dist/validation/property.validation.d.mts +1 -1
  22. package/dist/validation/property.validation.mjs +21 -17
  23. package/dist/validation/value.validation.d.mts +2 -2
  24. package/dist/validation/value.validation.mjs +63 -11
  25. package/package.json +3 -3
  26. package/src/constants.ts +84 -18
  27. package/src/helpers.ts +162 -4
  28. package/src/index.ts +3 -1
  29. package/src/models/infer.model.ts +105 -0
  30. package/src/models/misc.model.ts +212 -0
  31. package/src/models/schema.plain.model.ts +110 -0
  32. package/src/models/schema.typed.model.ts +109 -0
  33. package/src/models/transform.model.ts +85 -0
  34. package/src/models/validation.model.ts +123 -0
  35. package/src/schematic.ts +29 -18
  36. package/src/validation/property.validation.ts +46 -36
  37. package/src/validation/value.validation.ts +115 -15
  38. package/dist/models.d.mts +0 -507
  39. package/dist/models.mjs +0 -18
  40. package/src/models.ts +0 -691
@@ -0,0 +1,92 @@
1
+ import { Schematic } from "../schematic.mjs";
2
+ import { ExtractValueNames, ValueName, Values } from "./misc.model.mjs";
3
+ import { Constructor } from "@oscarpalmer/atoms/models";
4
+
5
+ //#region src/models/schema.plain.model.d.ts
6
+ /**
7
+ * A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
8
+ */
9
+ type PlainSchema = {
10
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
11
+ } & {
12
+ $required?: never;
13
+ $type?: never;
14
+ $validators?: never;
15
+ };
16
+ /**
17
+ * A schema for validating objects
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const schema: Schema = {
22
+ * name: 'string',
23
+ * age: 'number',
24
+ * tags: ['string', 'number'],
25
+ * };
26
+ * ```
27
+ */
28
+ type Schema = SchemaIndex;
29
+ /**
30
+ * A union of all valid types for a single schema entry
31
+ *
32
+ * Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
33
+ */
34
+ type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
35
+ /**
36
+ * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
37
+ */
38
+ interface SchemaIndex {
39
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
40
+ }
41
+ /**
42
+ * A property definition with explicit type(s), an optional requirement flag, and optional validators
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const prop: SchemaProperty = {
47
+ * $required: false,
48
+ * $type: ['string', 'number'],
49
+ * $validators: {
50
+ * string: (v) => v.length > 0,
51
+ * number: (v) => v > 0,
52
+ * },
53
+ * };
54
+ * ```
55
+ */
56
+ type SchemaProperty = {
57
+ /**
58
+ * Whether the property is required _(defaults to `true`)_
59
+ */
60
+ $required?: boolean;
61
+ /**
62
+ * The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
63
+ */
64
+ $type: SchemaPropertyType | SchemaPropertyType[];
65
+ /**
66
+ * Optional validators keyed by {@link ValueName}, applied during validation
67
+ */
68
+ $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
69
+ };
70
+ /**
71
+ * A union of valid types for a {@link SchemaProperty}'s `$type` field
72
+ *
73
+ * Can be a {@link Constructor}, {@link PlainSchema}, {@link Schematic}, {@link ValueName} string, or a custom validator function
74
+ */
75
+ type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
76
+ /**
77
+ * A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
78
+ *
79
+ * Each key may hold a single validator or an array of validators that receive the typed value
80
+ *
81
+ * @template Value - `$type` value(s) to derive validator keys from
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const validators: PropertyValidators<'string'> = {
86
+ * string: (value) => value.length > 0,
87
+ * };
88
+ * ```
89
+ */
90
+ type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
91
+ //#endregion
92
+ export { PlainSchema, PropertyValidators, Schema, SchemaEntry, SchemaIndex, SchemaProperty, SchemaPropertyType };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { ToSchemaPropertyType, ToSchemaType } from "./transform.model.mjs";
2
+ import { Schematic } from "../schematic.mjs";
3
+ import { PropertyValidators } from "./schema.plain.model.mjs";
4
+ import { OptionalKeys, RequiredKeys } from "./misc.model.mjs";
5
+ import { PlainObject, Simplify } from "@oscarpalmer/atoms/models";
6
+
7
+ //#region src/models/schema.typed.model.d.ts
8
+ /**
9
+ * A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
10
+ *
11
+ * @template Value - Property's type _(including `undefined`)_
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // For `{ name?: string }`, the `name` key produces:
16
+ * // TypedPropertyOptional<string | undefined>
17
+ * // => { $required: false; $type: 'string'; ... }
18
+ * ```
19
+ */
20
+ type TypedPropertyOptional<Value> = {
21
+ /**
22
+ * The property is not required
23
+ */
24
+ $required: false;
25
+ /**
26
+ * The type(s) of the property
27
+ */
28
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
29
+ /**
30
+ * Custom validators for the property and its types
31
+ */
32
+ $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
33
+ };
34
+ /**
35
+ * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
36
+ *
37
+ * @template Value - Property's type
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * // For `{ name: string }`, the `name` key produces:
42
+ * // TypedPropertyRequired<string>
43
+ * // => { $required?: true; $type: 'string'; ... }
44
+ * ```
45
+ */
46
+ type TypedPropertyRequired<Value> = {
47
+ /**
48
+ * The property is required _(defaults to `true`)_
49
+ */
50
+ $required?: true;
51
+ /**
52
+ * The type(s) of the property
53
+ */
54
+ $type: ToSchemaPropertyType<Value>;
55
+ /**
56
+ * Custom validators for the property and its types
57
+ */
58
+ $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
59
+ };
60
+ /**
61
+ * Creates a schema type constrained to match a TypeScript type
62
+ *
63
+ * Required keys map to {@link ToSchemaType} or {@link TypedPropertyRequired}; plain object values may also use {@link Schematic}. Optional keys map to {@link TypedPropertyOptional} or, for plain objects, {@link TypedSchemaOptional}
64
+ *
65
+ * @template Model - Object type to generate a schema for
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * type User = { name: string; age: number; bio?: string };
70
+ *
71
+ * const schema: TypedSchema<User> = {
72
+ * name: 'string',
73
+ * age: 'number',
74
+ * bio: { $required: false, $type: 'string' },
75
+ * };
76
+ * ```
77
+ */
78
+ type TypedSchema<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]> } & { [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]> }>;
79
+ /**
80
+ * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
81
+ *
82
+ * @template Model - Nested object type
83
+ */
84
+ type TypedSchemaOptional<Model extends PlainObject> = {
85
+ $required: false;
86
+ } & TypedSchema<Model>;
87
+ /**
88
+ * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
89
+ *
90
+ * @template Model - Nested object type
91
+ */
92
+ type TypedSchemaRequired<Model extends PlainObject> = {
93
+ $required?: true;
94
+ } & TypedSchema<Model>;
95
+ //#endregion
96
+ export { TypedPropertyOptional, TypedPropertyRequired, TypedSchema };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { TypedSchema } from "./schema.typed.model.mjs";
2
+ import { Schematic } from "../schematic.mjs";
3
+ import { DeduplicateTuple, UnionToTuple, UnwrapSingle, Values } from "./misc.model.mjs";
4
+ import { PlainObject } from "@oscarpalmer/atoms/models";
5
+
6
+ //#region src/models/transform.model.d.ts
7
+ /**
8
+ * Maps each element of a tuple through {@link ToValueType}
9
+ *
10
+ * @template Value - Tuple of types to map
11
+ */
12
+ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
13
+ /**
14
+ * Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
15
+ *
16
+ * @template Value - Tuple of types to map
17
+ */
18
+ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
19
+ /**
20
+ * Converts a type into its corresponding {@link SchemaPropertyType}-representation
21
+ *
22
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
23
+ *
24
+ * @template Value - type to convert
25
+ */
26
+ type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
27
+ /**
28
+ * Converts a single type to its schema property equivalent
29
+ *
30
+ * {@link NestedSchema} values have `$required` stripped, plain objects become {@link TypedSchema}, and primitives go through {@link ToValueType}
31
+ *
32
+ * @template Value - type to convert
33
+ */
34
+ type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
35
+ /**
36
+ * Converts a type into its corresponding {@link ValueName}-representation
37
+ *
38
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
39
+ *
40
+ * @template Value - type to convert
41
+ */
42
+ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
43
+ /**
44
+ * Maps a type to its {@link ValueName} string equivalent
45
+ *
46
+ * Resolves {@link Schematic} types as-is, then performs a reverse-lookup against {@link Values} _(excluding `'object'`)_ to find a matching key. If no match is found, `object` types resolve to `'object'` or a type-guard function, and all other unrecognised types resolve to a type-guard function
47
+ *
48
+ * @template Value - type to map
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // ToValueType<string> => 'string'
53
+ * // ToValueType<number[]> => 'array'
54
+ * // ToValueType<Date> => 'date'
55
+ * ```
56
+ */
57
+ type ToValueType<Value> = Value extends Schematic<any> ? Value : { [Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never }[keyof Omit<Values, 'object'>] extends infer Match ? [Match] extends [never] ? Value extends object ? 'object' | ((value: unknown) => value is Value) : (value: unknown) => value is Value : Match : never;
58
+ //#endregion
59
+ export { MapToSchemaPropertyTypes, MapToValueTypes, ToSchemaPropertyType, ToSchemaPropertyTypeEach, ToSchemaType, ToValueType };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ import { Schematic } from "../schematic.mjs";
2
+ import { ValueName } from "./misc.model.mjs";
3
+ import { GenericCallback } from "@oscarpalmer/atoms/models";
4
+
5
+ //#region src/models/validation.model.d.ts
6
+ type ReportingInformation = Record<ReportingType, boolean>;
7
+ type ReportingType = 'all' | 'first' | 'none' | 'throw';
8
+ /**
9
+ * A custom error class for schematic validation failures
10
+ */
11
+ declare class SchematicError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /**
15
+ * The runtime representation of a parsed schema property, used internally during validation
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const parsed: ValidatedProperty = {
20
+ * key: 'age',
21
+ * required: true,
22
+ * types: ['number'],
23
+ * validators: { number: [(v) => v > 0] },
24
+ * };
25
+ * ```
26
+ */
27
+ type ValidatedProperty = {
28
+ /**
29
+ * The property name in the schema
30
+ */
31
+ key: ValidatedPropertyKey;
32
+ /**
33
+ * Whether the property is required
34
+ */
35
+ required: boolean;
36
+ /**
37
+ * The allowed types for this property
38
+ */
39
+ types: ValidatedPropertyType[];
40
+ /**
41
+ * Custom validators grouped by {@link ValueName}
42
+ */
43
+ validators: ValidatedPropertyValidators;
44
+ };
45
+ /**
46
+ * Property name in schema
47
+ */
48
+ type ValidatedPropertyKey = {
49
+ /**
50
+ * Full property key, including parent keys for nested properties _(e.g., `address.street`)_
51
+ */
52
+ full: string;
53
+ /**
54
+ * The last segment of the property key _(e.g., `street` for `address.street`)_
55
+ */
56
+ short: string;
57
+ };
58
+ /**
59
+ * A union of valid types for a {@link ValidatedProperty}'s `types` array
60
+ *
61
+ * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
62
+ */
63
+ type ValidatedPropertyType = GenericCallback | ValidatedProperty[] | Schematic<unknown> | ValueName;
64
+ /**
65
+ * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
66
+ *
67
+ * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
68
+ */
69
+ type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
70
+ declare class ValidationError extends Error {
71
+ readonly information: ValidationInformation[];
72
+ constructor(information: ValidationInformation[]);
73
+ }
74
+ type ValidationInformation = {
75
+ key: ValidationInformationKey;
76
+ message: string;
77
+ validator?: GenericCallback;
78
+ };
79
+ type ValidationInformationKey = ValidatedPropertyKey;
80
+ //#endregion
81
+ export { ReportingInformation, ReportingType, SchematicError, ValidatedProperty, ValidatedPropertyKey, ValidatedPropertyType, ValidatedPropertyValidators, ValidationError, ValidationInformation, ValidationInformationKey };
@@ -0,0 +1,21 @@
1
+ import { NAME_ERROR_SCHEMATIC, NAME_ERROR_VALIDATION } from "../constants.mjs";
2
+ import { join } from "@oscarpalmer/atoms/string";
3
+ //#region src/models/validation.model.ts
4
+ /**
5
+ * A custom error class for schematic validation failures
6
+ */
7
+ var SchematicError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = NAME_ERROR_SCHEMATIC;
11
+ }
12
+ };
13
+ var ValidationError = class extends Error {
14
+ constructor(information) {
15
+ super(join(information.map((item) => item.message), "; "));
16
+ this.information = information;
17
+ this.name = NAME_ERROR_VALIDATION;
18
+ }
19
+ };
20
+ //#endregion
21
+ export { SchematicError, ValidationError };
@@ -1,4 +1,7 @@
1
- import { Infer, Schema, TypedSchema, ValidatedProperty } from "./models.mjs";
1
+ import { Infer } from "./models/infer.model.mjs";
2
+ import { TypedSchema } from "./models/schema.typed.model.mjs";
3
+ import { ValidatedProperty } from "./models/validation.model.mjs";
4
+ import { Schema } from "./models/schema.plain.model.mjs";
2
5
  import { PlainObject } from "@oscarpalmer/atoms/models";
3
6
 
4
7
  //#region src/schematic.d.ts
@@ -11,23 +14,34 @@ declare class Schematic<Model> {
11
14
  constructor(properties: ValidatedProperty[]);
12
15
  /**
13
16
  * Does the value match the schema?
14
- * @param value - Value to validate
17
+ *
18
+ * Will assert that the values matches the schema and throw an error if it does not. The error will contain all validation information for the first property that fails validation.
19
+ * @param value Value to validate
20
+ * @param errors Throws an error for the first validation failure
21
+ * @returns `true` if the value matches the schema, otherwise throws an error
22
+ */
23
+ is(value: unknown, errors: 'throw'): asserts value is Model;
24
+ /**
25
+ * Does the value match the schema?
26
+ *
27
+ * Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures.
28
+ * @param value Value to validate
15
29
  * @returns `true` if the value matches the schema, otherwise `false`
16
30
  */
17
31
  is(value: unknown): value is Model;
18
32
  }
19
33
  /**
20
34
  * Create a schematic from a schema
21
- * @template Model - Schema type
22
- * @param schema - Schema to create the schematic from
35
+ * @template Model Schema type
36
+ * @param schema Schema to create the schematic from
23
37
  * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
24
38
  * @returns A schematic for the given schema
25
39
  */
26
40
  declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
27
41
  /**
28
42
  * Create a schematic from a typed schema
29
- * @template Model - Existing type
30
- * @param schema - Typed schema to create the schematic from
43
+ * @template Model Existing type
44
+ * @param schema Typed schema to create the schematic from
31
45
  * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
32
46
  * @returns A schematic for the given typed schema
33
47
  */
@@ -1,6 +1,6 @@
1
- import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.mjs";
2
- import { isSchematic } from "./helpers.mjs";
3
- import { SchematicError } from "./models.mjs";
1
+ import { PROPERTY_SCHEMATIC, SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE } from "./constants.mjs";
2
+ import { getReporting, isSchematic } from "./helpers.mjs";
3
+ import { SchematicError } from "./models/validation.model.mjs";
4
4
  import { getProperties } from "./validation/property.validation.mjs";
5
5
  import { validateObject } from "./validation/value.validation.mjs";
6
6
  import { isPlainObject } from "@oscarpalmer/atoms/is";
@@ -11,21 +11,16 @@ import { isPlainObject } from "@oscarpalmer/atoms/is";
11
11
  var Schematic = class {
12
12
  #properties;
13
13
  constructor(properties) {
14
- Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
14
+ Object.defineProperty(this, PROPERTY_SCHEMATIC, { value: true });
15
15
  this.#properties = properties;
16
16
  }
17
- /**
18
- * Does the value match the schema?
19
- * @param value - Value to validate
20
- * @returns `true` if the value matches the schema, otherwise `false`
21
- */
22
- is(value) {
23
- return validateObject(value, this.#properties);
17
+ is(value, errors) {
18
+ return validateObject(value, this.#properties, getReporting(errors));
24
19
  }
25
20
  };
26
21
  function schematic(schema) {
27
22
  if (isSchematic(schema)) return schema;
28
- if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
23
+ if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
29
24
  return new Schematic(getProperties(schema));
30
25
  }
31
26
  //#endregion
@@ -1,4 +1,4 @@
1
- import { ValidatedProperty } from "../models.mjs";
1
+ import { ValidatedProperty } from "../models/validation.model.mjs";
2
2
  import { PlainObject } from "@oscarpalmer/atoms/models";
3
3
 
4
4
  //#region src/validation/property.validation.d.ts
@@ -1,6 +1,6 @@
1
- import { EXPRESSION_PROPERTY, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, TEMPLATE_PATTERN_KEY, TEMPLATE_PATTERN_PROPERTY, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.mjs";
1
+ import { PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE, TYPE_ALL, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.mjs";
2
2
  import { instanceOf, isSchematic } from "../helpers.mjs";
3
- import { SchematicError } from "../models.mjs";
3
+ import { SchematicError } from "../models/validation.model.mjs";
4
4
  import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
5
5
  import { join } from "@oscarpalmer/atoms/string";
6
6
  //#region src/validation/property.validation.ts
@@ -10,32 +10,36 @@ function getDisallowedProperty(obj) {
10
10
  if ("$validators" in obj) return PROPERTY_VALIDATORS;
11
11
  }
12
12
  function getProperties(original, prefix, fromType) {
13
- if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
13
+ if (Object.keys(original).length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
14
14
  if (fromType ?? false) {
15
15
  const property = getDisallowedProperty(original);
16
- if (property != null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(TEMPLATE_PATTERN_KEY, prefix).replace(TEMPLATE_PATTERN_PROPERTY, property));
16
+ if (property != null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace("<>", prefix).replace("<>", property));
17
17
  }
18
18
  const keys = Object.keys(original);
19
19
  const keysLength = keys.length;
20
20
  const properties = [];
21
21
  for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
22
22
  const key = keys[keyIndex];
23
- if (EXPRESSION_PROPERTY.test(key)) continue;
23
+ const prefixed = join([prefix, key], ".");
24
24
  const value = original[key];
25
+ if (value == null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", prefixed));
25
26
  const types = [];
26
27
  let required = true;
27
28
  let validators = {};
28
29
  if (isPlainObject(value)) {
29
30
  required = getRequired(key, value) ?? required;
30
31
  validators = getValidators(value[PROPERTY_VALIDATORS]);
31
- if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
32
- else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
32
+ const hasType = PROPERTY_TYPE in value;
33
+ types.push(...getTypes(key, hasType ? value[PROPERTY_TYPE] : value, prefix, hasType));
33
34
  } else types.push(...getTypes(key, value, prefix));
34
35
  if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
35
36
  properties.push({
36
- key,
37
37
  types,
38
38
  validators,
39
+ key: {
40
+ full: prefixed,
41
+ short: key
42
+ },
39
43
  required: required && !types.includes("undefined")
40
44
  });
41
45
  }
@@ -43,7 +47,7 @@ function getProperties(original, prefix, fromType) {
43
47
  }
44
48
  function getRequired(key, obj) {
45
49
  if (!("$required" in obj)) return;
46
- if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
50
+ if (typeof obj["$required"] !== "boolean") throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
47
51
  return obj[PROPERTY_REQUIRED];
48
52
  }
49
53
  function getTypes(key, original, prefix, fromType) {
@@ -57,7 +61,7 @@ function getTypes(key, original, prefix, fromType) {
57
61
  types.push(isConstructor(value) ? instanceOf(value) : value);
58
62
  break;
59
63
  case isPlainObject(value):
60
- types.push(...getProperties(value, join([prefix, key], "."), fromType));
64
+ types.push(getProperties(value, join([prefix, key], "."), fromType));
61
65
  break;
62
66
  case isSchematic(value):
63
67
  types.push(value);
@@ -65,25 +69,25 @@ function getTypes(key, original, prefix, fromType) {
65
69
  case TYPE_ALL.has(value):
66
70
  types.push(value);
67
71
  break;
68
- default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
72
+ default: throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
69
73
  }
70
74
  }
71
- if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
75
+ if (types.length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
72
76
  return types;
73
77
  }
74
78
  function getValidators(original) {
75
79
  const validators = {};
76
80
  if (original == null) return validators;
77
- if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
81
+ if (!isPlainObject(original)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
78
82
  const keys = Object.keys(original);
79
83
  const { length } = keys;
80
84
  for (let index = 0; index < length; index += 1) {
81
85
  const key = keys[index];
82
- if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
86
+ if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
83
87
  const value = original[key];
84
- validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
85
- if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
86
- return true;
88
+ validators[key] = (Array.isArray(value) ? value : [value]).map((item) => {
89
+ if (typeof item !== "function") throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
90
+ return item;
87
91
  });
88
92
  }
89
93
  return validators;
@@ -1,6 +1,6 @@
1
- import { ValidatedProperty } from "../models.mjs";
1
+ import { ReportingInformation, ValidatedProperty, ValidationInformation } from "../models/validation.model.mjs";
2
2
 
3
3
  //#region src/validation/value.validation.d.ts
4
- declare function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean;
4
+ declare function validateObject(obj: unknown, properties: ValidatedProperty[], reporting: ReportingInformation, validation?: ValidationInformation[]): boolean;
5
5
  //#endregion
6
6
  export { validateObject };
@@ -1,30 +1,82 @@
1
- import { isSchematic } from "../helpers.mjs";
1
+ import { getInvalidInputMessage, getInvalidMissingMessage, getInvalidTypeMessage, getInvalidValidatorMessage, isSchematic } from "../helpers.mjs";
2
+ import { ValidationError } from "../models/validation.model.mjs";
2
3
  import { isPlainObject } from "@oscarpalmer/atoms/is";
3
4
  //#region src/validation/value.validation.ts
4
- function validateObject(obj, properties) {
5
- if (!isPlainObject(obj)) return false;
5
+ function validateNamed(property, name, value, validation) {
6
+ if (!validators[name](value)) return false;
7
+ const propertyValidators = property.validators[name];
8
+ if (propertyValidators == null || propertyValidators.length === 0) return true;
9
+ const { length } = propertyValidators;
10
+ for (let index = 0; index < length; index += 1) {
11
+ const validator = propertyValidators[index];
12
+ if (!validator(value)) {
13
+ validation.push({
14
+ key: { ...property.key },
15
+ message: getInvalidValidatorMessage(property, name, index, length),
16
+ validator
17
+ });
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+ function validateObject(obj, properties, reporting, validation) {
24
+ if (!isPlainObject(obj)) {
25
+ if (reporting.throw && validation == null) throw new ValidationError([{
26
+ key: {
27
+ full: "",
28
+ short: ""
29
+ },
30
+ message: getInvalidInputMessage(obj)
31
+ }]);
32
+ return false;
33
+ }
6
34
  const propertiesLength = properties.length;
7
35
  outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
8
36
  const property = properties[propertyIndex];
9
37
  const { key, required, types } = property;
10
- const value = obj[key];
11
- if (value === void 0 && required) return false;
38
+ const value = obj[key.short];
39
+ if (value === void 0 && required) {
40
+ const information = {
41
+ key: { ...key },
42
+ message: getInvalidMissingMessage(property)
43
+ };
44
+ if (reporting.throw && validation == null) throw new ValidationError([information]);
45
+ if (validation != null) validation.push(information);
46
+ return false;
47
+ }
12
48
  const typesLength = types.length;
49
+ const information = [];
13
50
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
14
51
  const type = types[typeIndex];
15
- if (validateValue(type, property, value)) continue outer;
52
+ if (validateValue(type, property, value, reporting, information)) continue outer;
16
53
  }
54
+ if (reporting.throw && validation == null) throw new ValidationError(information.length === 0 ? [{
55
+ key: { ...key },
56
+ message: getInvalidTypeMessage(property, value)
57
+ }] : information);
58
+ validation?.push(...information);
17
59
  return false;
18
60
  }
19
61
  return true;
20
62
  }
21
- function validateValue(type, property, value) {
63
+ function validateValue(type, property, value, reporting, validation) {
64
+ let result;
22
65
  switch (true) {
23
- case isSchematic(type): return type.is(value);
24
- case typeof type === "function": return type(value);
25
- case typeof type === "object": return validateObject(value, [type]);
26
- default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
66
+ case typeof type === "function":
67
+ result = type(value);
68
+ break;
69
+ case Array.isArray(type):
70
+ result = validateObject(value, type, reporting, validation);
71
+ break;
72
+ case isSchematic(type):
73
+ result = type.is(value, reporting);
74
+ break;
75
+ default:
76
+ result = validateNamed(property, type, value, validation);
77
+ break;
27
78
  }
79
+ return result;
28
80
  }
29
81
  const validators = {
30
82
  array: Array.isArray,