@oscarpalmer/jhunal 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.mts +28 -15
- package/dist/constants.mjs +31 -14
- package/dist/helpers.d.mts +8 -1
- package/dist/helpers.mjs +68 -3
- package/dist/index.d.mts +304 -240
- package/dist/index.mjs +201 -51
- package/dist/models/infer.model.d.mts +66 -0
- package/dist/models/infer.model.mjs +1 -0
- package/dist/models/misc.model.d.mts +153 -0
- package/dist/models/misc.model.mjs +1 -0
- package/dist/models/schema.plain.model.d.mts +92 -0
- package/dist/models/schema.plain.model.mjs +1 -0
- package/dist/models/schema.typed.model.d.mts +96 -0
- package/dist/models/schema.typed.model.mjs +1 -0
- package/dist/models/transform.model.d.mts +59 -0
- package/dist/models/transform.model.mjs +1 -0
- package/dist/models/validation.model.d.mts +82 -0
- package/dist/models/validation.model.mjs +21 -0
- package/dist/schematic.d.mts +34 -1
- package/dist/schematic.mjs +11 -12
- package/dist/validation/property.validation.d.mts +1 -1
- package/dist/validation/property.validation.mjs +21 -17
- package/dist/validation/value.validation.d.mts +2 -2
- package/dist/validation/value.validation.mjs +73 -12
- package/package.json +2 -2
- package/src/constants.ts +84 -19
- package/src/helpers.ts +162 -4
- package/src/index.ts +3 -1
- package/src/models/infer.model.ts +105 -0
- package/src/models/misc.model.ts +212 -0
- package/src/models/schema.plain.model.ts +110 -0
- package/src/models/schema.typed.model.ts +109 -0
- package/src/models/transform.model.ts +85 -0
- package/src/models/validation.model.ts +124 -0
- package/src/schematic.ts +55 -10
- package/src/validation/property.validation.ts +41 -36
- package/src/validation/value.validation.ts +133 -20
- package/dist/models.d.mts +0 -484
- package/dist/models.mjs +0 -13
- package/src/models.ts +0 -665
|
@@ -1,29 +1,90 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../constants.mjs";
|
|
2
|
+
import { getInvalidInputMessage, getInvalidMissingMessage, getInvalidTypeMessage, getInvalidValidatorMessage, isSchematic } from "../helpers.mjs";
|
|
3
|
+
import { ValidationError } from "../models/validation.model.mjs";
|
|
2
4
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
3
5
|
//#region src/validation/value.validation.ts
|
|
4
|
-
function
|
|
5
|
-
if (!
|
|
6
|
+
function validateNamed(property, name, value, validation) {
|
|
7
|
+
if (!validators[name](value)) return false;
|
|
8
|
+
const propertyValidators = property.validators[name];
|
|
9
|
+
if (propertyValidators == null || propertyValidators.length === 0) return true;
|
|
10
|
+
const { length } = propertyValidators;
|
|
11
|
+
for (let index = 0; index < length; index += 1) {
|
|
12
|
+
const validator = propertyValidators[index];
|
|
13
|
+
if (!validator(value)) {
|
|
14
|
+
validation.push({
|
|
15
|
+
value,
|
|
16
|
+
key: { ...property.key },
|
|
17
|
+
message: getInvalidValidatorMessage(property, name, index, length),
|
|
18
|
+
validator
|
|
19
|
+
});
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
function validateObject(obj, properties, reporting, property, validation) {
|
|
26
|
+
if (!isPlainObject(obj)) {
|
|
27
|
+
const information = {
|
|
28
|
+
key: {
|
|
29
|
+
full: "",
|
|
30
|
+
short: ""
|
|
31
|
+
},
|
|
32
|
+
message: property == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage(property, obj),
|
|
33
|
+
value: obj
|
|
34
|
+
};
|
|
35
|
+
if (reporting.throw) throw new ValidationError([information]);
|
|
36
|
+
return reporting.none ? false : [information];
|
|
37
|
+
}
|
|
38
|
+
const allInformation = [];
|
|
6
39
|
const propertiesLength = properties.length;
|
|
7
40
|
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
8
41
|
const property = properties[propertyIndex];
|
|
9
42
|
const { key, required, types } = property;
|
|
10
|
-
const value = obj[key];
|
|
11
|
-
if (value === void 0 && required)
|
|
43
|
+
const value = obj[key.short];
|
|
44
|
+
if (value === void 0 && required) {
|
|
45
|
+
const information = {
|
|
46
|
+
value,
|
|
47
|
+
key: { ...key },
|
|
48
|
+
message: getInvalidMissingMessage(property)
|
|
49
|
+
};
|
|
50
|
+
if (reporting.throw && validation == null) throw new ValidationError([information]);
|
|
51
|
+
if (validation != null) validation.push(information);
|
|
52
|
+
if (reporting.all) {
|
|
53
|
+
allInformation.push(information);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
return reporting.none ? false : [information];
|
|
57
|
+
}
|
|
12
58
|
const typesLength = types.length;
|
|
59
|
+
const information = [];
|
|
13
60
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
14
61
|
const type = types[typeIndex];
|
|
15
|
-
if (validateValue(type, property, value)) continue outer;
|
|
62
|
+
if (validateValue(type, property, value, reporting, information)) continue outer;
|
|
63
|
+
}
|
|
64
|
+
if (information.length === 0) information.push({
|
|
65
|
+
value,
|
|
66
|
+
key: { ...key },
|
|
67
|
+
message: getInvalidTypeMessage(property, value)
|
|
68
|
+
});
|
|
69
|
+
if (reporting.throw && validation == null) throw new ValidationError(information);
|
|
70
|
+
validation?.push(...information);
|
|
71
|
+
if (reporting.all) {
|
|
72
|
+
allInformation.push(...information);
|
|
73
|
+
continue;
|
|
16
74
|
}
|
|
17
|
-
return false;
|
|
75
|
+
return reporting.none ? false : information;
|
|
18
76
|
}
|
|
19
|
-
return true;
|
|
77
|
+
return reporting.none ? true : allInformation.length === 0 ? true : allInformation;
|
|
20
78
|
}
|
|
21
|
-
function validateValue(type, property, value) {
|
|
79
|
+
function validateValue(type, property, value, reporting, validation) {
|
|
22
80
|
switch (true) {
|
|
23
|
-
case isSchematic(type): return type.is(value);
|
|
24
81
|
case typeof type === "function": return type(value);
|
|
25
|
-
case
|
|
26
|
-
|
|
82
|
+
case Array.isArray(type): {
|
|
83
|
+
const nested = validateObject(value, type, reporting, property, validation);
|
|
84
|
+
return typeof nested !== "boolean" ? false : nested;
|
|
85
|
+
}
|
|
86
|
+
case isSchematic(type): return type.is(value, reporting);
|
|
87
|
+
default: return validateNamed(property, type, value, validation);
|
|
27
88
|
}
|
|
28
89
|
}
|
|
29
90
|
const validators = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oscarpalmer/jhunal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Flies free beneath the glistening moons…",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schema",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"watch": "npx vite build --watch"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@oscarpalmer/atoms": "^0.
|
|
41
|
+
"@oscarpalmer/atoms": "^0.169"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^25.5",
|
package/src/constants.ts
CHANGED
|
@@ -1,43 +1,106 @@
|
|
|
1
|
-
import type {ValueName} from './models';
|
|
1
|
+
import type {ValueName} from './models/misc.model';
|
|
2
|
+
import type {ReportingType} from './models/validation.model';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// #region Misc.
|
|
4
5
|
|
|
5
6
|
export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// #endregion
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
"'<key>.<property>' property is not allowed for schemas in $type";
|
|
10
|
+
// #region Names
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
"'<>' property must not be 'null' or 'undefined'";
|
|
14
|
-
|
|
15
|
-
export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
12
|
+
export const NAME_SCHEMATIC = 'Schematic';
|
|
16
13
|
|
|
17
|
-
export const
|
|
14
|
+
export const NAME_ERROR_SCHEMATIC = 'SchematicError';
|
|
18
15
|
|
|
19
|
-
export const
|
|
16
|
+
export const NAME_ERROR_VALIDATION = 'ValidationError';
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
// #endregion
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export const MESSAGE_VALIDATOR_INVALID_VALUE =
|
|
26
|
-
"Validator '<>' must be a function or an array of functions";
|
|
20
|
+
// #region Properties
|
|
27
21
|
|
|
28
22
|
export const PROPERTY_REQUIRED = '$required';
|
|
29
23
|
|
|
24
|
+
export const PROPERTY_SCHEMATIC = '$schematic';
|
|
25
|
+
|
|
30
26
|
export const PROPERTY_TYPE = '$type';
|
|
31
27
|
|
|
32
28
|
export const PROPERTY_VALIDATORS = '$validators';
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
// #endregion
|
|
31
|
+
|
|
32
|
+
// #region Property validation
|
|
33
|
+
|
|
34
|
+
export const VALIDATION_MESSAGE_INVALID_INPUT = "Expected 'object' as input but received <>";
|
|
35
|
+
|
|
36
|
+
export const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '<>'";
|
|
37
|
+
|
|
38
|
+
export const VALIDATION_MESSAGE_INVALID_TYPE = "Expected <> for '<>' but received <>";
|
|
39
|
+
|
|
40
|
+
export const VALIDATION_MESSAGE_INVALID_VALUE =
|
|
41
|
+
"Value does not satisfy validator for '<>' and type '<>'";
|
|
42
|
+
|
|
43
|
+
export const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = ' at index <>';
|
|
44
|
+
|
|
45
|
+
// #endregion
|
|
46
|
+
|
|
47
|
+
// #region Reporting
|
|
48
|
+
|
|
49
|
+
export const REPORTING_ALL: ReportingType = 'all';
|
|
50
|
+
|
|
51
|
+
export const REPORTING_FIRST: ReportingType = 'first';
|
|
52
|
+
|
|
53
|
+
export const REPORTING_NONE: ReportingType = 'none';
|
|
54
|
+
|
|
55
|
+
export const REPORTING_THROW: ReportingType = 'throw';
|
|
56
|
+
|
|
57
|
+
export const REPORTING_TYPES = new Set<ReportingType>([
|
|
58
|
+
REPORTING_ALL,
|
|
59
|
+
REPORTING_FIRST,
|
|
60
|
+
REPORTING_NONE,
|
|
61
|
+
REPORTING_THROW,
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// #endregion
|
|
65
|
+
|
|
66
|
+
// #region Schematic validation
|
|
67
|
+
|
|
68
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
|
|
69
|
+
|
|
70
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED =
|
|
71
|
+
"'<>.<>' property is not allowed for schemas in $type";
|
|
72
|
+
|
|
73
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE =
|
|
74
|
+
"'<>' property must not be 'null' or 'undefined'";
|
|
75
|
+
|
|
76
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED =
|
|
77
|
+
"'<>.$required' property must be a boolean";
|
|
78
|
+
|
|
79
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE =
|
|
80
|
+
"'<>' property must be of a valid type";
|
|
81
|
+
|
|
82
|
+
export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
|
|
83
|
+
|
|
84
|
+
export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
85
|
+
|
|
86
|
+
export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
|
|
87
|
+
|
|
88
|
+
export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE =
|
|
89
|
+
"Validator '<>' must be a function or an array of functions";
|
|
90
|
+
|
|
91
|
+
// #endregion
|
|
92
|
+
|
|
93
|
+
// #region Templates
|
|
35
94
|
|
|
36
95
|
export const TEMPLATE_PATTERN = '<>';
|
|
37
96
|
|
|
38
|
-
|
|
97
|
+
// #endregion
|
|
98
|
+
|
|
99
|
+
// #region Types
|
|
39
100
|
|
|
40
|
-
export const
|
|
101
|
+
export const TYPE_ARRAY = 'array';
|
|
102
|
+
|
|
103
|
+
export const TYPE_NULL = 'null';
|
|
41
104
|
|
|
42
105
|
export const TYPE_OBJECT = 'object';
|
|
43
106
|
|
|
@@ -56,3 +119,5 @@ export const VALIDATABLE_TYPES = new Set<ValueName>([
|
|
|
56
119
|
]);
|
|
57
120
|
|
|
58
121
|
export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
|
|
122
|
+
|
|
123
|
+
// #endregion
|
package/src/helpers.ts
CHANGED
|
@@ -1,8 +1,134 @@
|
|
|
1
|
-
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {Constructor} from '@oscarpalmer/atoms/models';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
MESSAGE_CONSTRUCTOR,
|
|
5
|
+
NAME_SCHEMATIC,
|
|
6
|
+
PROPERTY_SCHEMATIC,
|
|
7
|
+
REPORTING_ALL,
|
|
8
|
+
REPORTING_FIRST,
|
|
9
|
+
REPORTING_NONE,
|
|
10
|
+
REPORTING_THROW,
|
|
11
|
+
REPORTING_TYPES,
|
|
12
|
+
TEMPLATE_PATTERN,
|
|
13
|
+
TYPE_ARRAY,
|
|
14
|
+
TYPE_NULL,
|
|
15
|
+
TYPE_OBJECT,
|
|
16
|
+
TYPE_UNDEFINED,
|
|
17
|
+
VALIDATION_MESSAGE_INVALID_INPUT,
|
|
18
|
+
VALIDATION_MESSAGE_INVALID_REQUIRED,
|
|
19
|
+
VALIDATION_MESSAGE_INVALID_TYPE,
|
|
20
|
+
VALIDATION_MESSAGE_INVALID_VALUE,
|
|
21
|
+
VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX,
|
|
22
|
+
} from './constants';
|
|
23
|
+
import type {ValueName} from './models/misc.model';
|
|
24
|
+
import type {
|
|
25
|
+
ReportingInformation,
|
|
26
|
+
ReportingType,
|
|
27
|
+
ValidatedProperty,
|
|
28
|
+
ValidatedPropertyType,
|
|
29
|
+
} from './models/validation.model';
|
|
4
30
|
import type {Schematic} from './schematic';
|
|
5
31
|
|
|
32
|
+
export function getInvalidInputMessage(actual: unknown): string {
|
|
33
|
+
return VALIDATION_MESSAGE_INVALID_INPUT.replace(TEMPLATE_PATTERN, getValueType(actual));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getInvalidMissingMessage(property: ValidatedProperty): string {
|
|
37
|
+
let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace(
|
|
38
|
+
TEMPLATE_PATTERN,
|
|
39
|
+
renderTypes(property.types),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
message = message.replace(TEMPLATE_PATTERN, property.key.full);
|
|
43
|
+
|
|
44
|
+
return message;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getInvalidTypeMessage(property: ValidatedProperty, actual: unknown): string {
|
|
48
|
+
let message = VALIDATION_MESSAGE_INVALID_TYPE.replace(
|
|
49
|
+
TEMPLATE_PATTERN,
|
|
50
|
+
renderTypes(property.types),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
message = message.replace(TEMPLATE_PATTERN, property.key.full);
|
|
54
|
+
message = message.replace(TEMPLATE_PATTERN, getValueType(actual));
|
|
55
|
+
|
|
56
|
+
return message;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getInvalidValidatorMessage(
|
|
60
|
+
property: ValidatedProperty,
|
|
61
|
+
type: ValueName,
|
|
62
|
+
index: number,
|
|
63
|
+
length: number,
|
|
64
|
+
): string {
|
|
65
|
+
let message = VALIDATION_MESSAGE_INVALID_VALUE.replace(TEMPLATE_PATTERN, property.key.full);
|
|
66
|
+
|
|
67
|
+
message = message.replace(TEMPLATE_PATTERN, type);
|
|
68
|
+
|
|
69
|
+
if (length > 1) {
|
|
70
|
+
message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace(TEMPLATE_PATTERN, String(index));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return message;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getPropertyType(original: ValidatedPropertyType): string {
|
|
77
|
+
if (typeof original === 'function') {
|
|
78
|
+
return 'a validated value';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(original)) {
|
|
82
|
+
return `'${TYPE_OBJECT}'`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isSchematic(original)) {
|
|
86
|
+
return `a ${NAME_SCHEMATIC}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return `'${String(original)}'`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getReporting(value: unknown): ReportingInformation {
|
|
93
|
+
const type = REPORTING_TYPES.has(value as ReportingType)
|
|
94
|
+
? (value as ReportingType)
|
|
95
|
+
: REPORTING_NONE;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
[REPORTING_ALL]: type === REPORTING_ALL,
|
|
99
|
+
[REPORTING_FIRST]: type === REPORTING_FIRST,
|
|
100
|
+
[REPORTING_NONE]: type === REPORTING_NONE,
|
|
101
|
+
[REPORTING_THROW]: type === REPORTING_THROW,
|
|
102
|
+
} as ReportingInformation;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getValueType(value: unknown): string {
|
|
106
|
+
const valueType = typeof value;
|
|
107
|
+
|
|
108
|
+
switch (true) {
|
|
109
|
+
case value === null:
|
|
110
|
+
return `'${TYPE_NULL}'`;
|
|
111
|
+
|
|
112
|
+
case value === undefined:
|
|
113
|
+
return `'${TYPE_UNDEFINED}'`;
|
|
114
|
+
|
|
115
|
+
case valueType !== TYPE_OBJECT:
|
|
116
|
+
return `'${valueType}'`;
|
|
117
|
+
|
|
118
|
+
case Array.isArray(value):
|
|
119
|
+
return `'${TYPE_ARRAY}'`;
|
|
120
|
+
|
|
121
|
+
case isPlainObject(value):
|
|
122
|
+
return `'${TYPE_OBJECT}'`;
|
|
123
|
+
|
|
124
|
+
case isSchematic(value):
|
|
125
|
+
return `a ${NAME_SCHEMATIC}`;
|
|
126
|
+
|
|
127
|
+
default:
|
|
128
|
+
return value.constructor.name;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
6
132
|
/**
|
|
7
133
|
* Creates a validator function for a given constructor
|
|
8
134
|
* @param constructor - Constructor to check against
|
|
@@ -30,7 +156,39 @@ export function isSchematic(value: unknown): value is Schematic<never> {
|
|
|
30
156
|
return (
|
|
31
157
|
typeof value === 'object' &&
|
|
32
158
|
value !== null &&
|
|
33
|
-
|
|
34
|
-
value[
|
|
159
|
+
PROPERTY_SCHEMATIC in value &&
|
|
160
|
+
value[PROPERTY_SCHEMATIC] === true
|
|
35
161
|
);
|
|
36
162
|
}
|
|
163
|
+
|
|
164
|
+
function renderTypes(types: ValidatedPropertyType[]): string {
|
|
165
|
+
const unique = new Set<string>();
|
|
166
|
+
const parts: string[] = [];
|
|
167
|
+
|
|
168
|
+
for (let index = 0; index < types.length; index += 1) {
|
|
169
|
+
const rendered = getPropertyType(types[index]);
|
|
170
|
+
|
|
171
|
+
if (unique.has(rendered)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
unique.add(rendered);
|
|
176
|
+
parts.push(rendered);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const {length} = parts;
|
|
180
|
+
|
|
181
|
+
let rendered = '';
|
|
182
|
+
|
|
183
|
+
for (let index = 0; index < length; index += 1) {
|
|
184
|
+
rendered += parts[index];
|
|
185
|
+
|
|
186
|
+
if (index < length - 2) {
|
|
187
|
+
rendered += ', ';
|
|
188
|
+
} else if (index === length - 2) {
|
|
189
|
+
rendered += parts.length > 2 ? ', or ' : ' or ';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return rendered;
|
|
194
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export {instanceOf, isSchematic} from './helpers';
|
|
2
|
-
export
|
|
2
|
+
export type {Schema} from './models/schema.plain.model';
|
|
3
|
+
export type {TypedSchema} from './models/schema.typed.model';
|
|
4
|
+
export {SchematicError, ValidationError} from './models/validation.model';
|
|
3
5
|
export {schematic, type Schematic} from './schematic';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type {Constructor, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import type {Schematic} from '../schematic';
|
|
3
|
+
import type {IsOptionalProperty, ValueName, Values} from './misc.model';
|
|
4
|
+
import type {PlainSchema, Schema, SchemaProperty} from './schema.plain.model';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Infers the TypeScript type from a {@link Schema} definition
|
|
8
|
+
*
|
|
9
|
+
* @template Model - Schema to infer types from
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const userSchema = {
|
|
14
|
+
* name: 'string',
|
|
15
|
+
* age: 'number',
|
|
16
|
+
* address: { $required: false, $type: 'string' },
|
|
17
|
+
* } satisfies Schema;
|
|
18
|
+
*
|
|
19
|
+
* type User = Infer<typeof userSchema>;
|
|
20
|
+
* // { name: string; age: number; address?: string }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export type Infer<Model extends Schema> = Simplify<
|
|
24
|
+
{
|
|
25
|
+
[Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
|
|
26
|
+
} & {
|
|
27
|
+
[Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
|
|
28
|
+
}
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
|
|
33
|
+
*
|
|
34
|
+
* @template Model - {@link Schema} to extract optional keys from
|
|
35
|
+
*/
|
|
36
|
+
export type InferOptionalKeys<Model extends Schema> = keyof {
|
|
37
|
+
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
|
|
42
|
+
*
|
|
43
|
+
* @template Value - `$type` value _(single or array)_
|
|
44
|
+
*/
|
|
45
|
+
export type InferPropertyType<Value> = Value extends (infer Item)[]
|
|
46
|
+
? InferPropertyValue<Item>
|
|
47
|
+
: InferPropertyValue<Value>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Maps a single type definition to its TypeScript equivalent
|
|
51
|
+
*
|
|
52
|
+
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
|
|
53
|
+
*
|
|
54
|
+
* @template Value - single type definition
|
|
55
|
+
*/
|
|
56
|
+
export type InferPropertyValue<Value> =
|
|
57
|
+
Value extends Constructor<infer Instance>
|
|
58
|
+
? Instance
|
|
59
|
+
: Value extends Schematic<infer Model>
|
|
60
|
+
? Model
|
|
61
|
+
: Value extends ValueName
|
|
62
|
+
? Values[Value & ValueName]
|
|
63
|
+
: Value extends Schema
|
|
64
|
+
? Infer<Value>
|
|
65
|
+
: never;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
|
|
69
|
+
*
|
|
70
|
+
* @template Model - Schema to extract required keys from
|
|
71
|
+
*/
|
|
72
|
+
export type InferRequiredKeys<Model extends Schema> = keyof {
|
|
73
|
+
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
|
|
78
|
+
*
|
|
79
|
+
* @template Value - Schema entry value _(single or array)_
|
|
80
|
+
*/
|
|
81
|
+
export type InferSchemaEntry<Value> = Value extends (infer Item)[]
|
|
82
|
+
? InferSchemaEntryValue<Item>
|
|
83
|
+
: InferSchemaEntryValue<Value>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolves a single schema entry to its TypeScript type
|
|
87
|
+
*
|
|
88
|
+
* Handles, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link NestedSchema} objects, {@link ValueName} strings, and plain {@link Schema} objects
|
|
89
|
+
*
|
|
90
|
+
* @template Value - single schema entry
|
|
91
|
+
*/
|
|
92
|
+
export type InferSchemaEntryValue<Value> =
|
|
93
|
+
Value extends Constructor<infer Instance>
|
|
94
|
+
? Instance
|
|
95
|
+
: Value extends Schematic<infer Model>
|
|
96
|
+
? Model
|
|
97
|
+
: Value extends SchemaProperty
|
|
98
|
+
? InferPropertyType<Value['$type']>
|
|
99
|
+
: Value extends PlainSchema
|
|
100
|
+
? Infer<Value & Schema>
|
|
101
|
+
: Value extends ValueName
|
|
102
|
+
? Values[Value & ValueName]
|
|
103
|
+
: Value extends Schema
|
|
104
|
+
? Infer<Value>
|
|
105
|
+
: never;
|