@oscarpalmer/jhunal 0.11.0 → 0.13.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.js +6 -4
- package/dist/{is.js → helpers.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/jhunal.full.js +83 -158
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/array/compact.js +12 -0
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/string.js +24 -0
- package/dist/schematic.js +9 -7
- package/dist/validation/property.validation.js +82 -0
- package/dist/validation/value.validation.js +23 -35
- package/package.json +1 -1
- package/src/constants.ts +10 -4
- package/src/{is.ts → helpers.ts} +1 -1
- package/src/index.ts +1 -1
- package/src/models.ts +21 -13
- package/src/schematic.ts +13 -10
- package/src/validation/property.validation.ts +187 -0
- package/src/validation/value.validation.ts +37 -61
- package/types/constants.d.ts +4 -2
- package/types/{is.d.ts → helpers.d.ts} +1 -1
- package/types/index.d.ts +1 -1
- package/types/models.d.ts +6 -12
- package/types/schematic.d.ts +2 -2
- package/types/validation/property.validation.d.ts +3 -0
- package/types/validation/value.validation.d.ts +2 -3
- package/dist/validation/schema.validation.js +0 -105
- package/src/validation/schema.validation.ts +0 -233
- package/types/validation/schema.validation.d.ts +0 -2
package/src/models.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type {GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {ERROR_NAME} from './constants';
|
|
3
3
|
import type {Schematic} from './schematic';
|
|
4
4
|
|
|
@@ -122,10 +122,16 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
122
122
|
*/
|
|
123
123
|
export type Schema = SchemaIndex;
|
|
124
124
|
|
|
125
|
-
type SchemaEntry =
|
|
125
|
+
type SchemaEntry =
|
|
126
|
+
| Constructor
|
|
127
|
+
| Schema
|
|
128
|
+
| SchemaProperty
|
|
129
|
+
| Schematic<unknown>
|
|
130
|
+
| ValueName
|
|
131
|
+
| ((value: unknown) => boolean);
|
|
126
132
|
|
|
127
133
|
interface SchemaIndex {
|
|
128
|
-
[key: string]: SchemaEntry | SchemaEntry[];
|
|
134
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/**
|
|
@@ -137,7 +143,12 @@ export type SchemaProperty = {
|
|
|
137
143
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
138
144
|
};
|
|
139
145
|
|
|
140
|
-
type SchemaPropertyType =
|
|
146
|
+
type SchemaPropertyType =
|
|
147
|
+
| Constructor
|
|
148
|
+
| Schema
|
|
149
|
+
| Schematic<unknown>
|
|
150
|
+
| ValueName
|
|
151
|
+
| ((value: unknown) => boolean);
|
|
141
152
|
|
|
142
153
|
export class SchematicError extends Error {
|
|
143
154
|
constructor(message: string) {
|
|
@@ -279,25 +290,22 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
|
279
290
|
: Value;
|
|
280
291
|
|
|
281
292
|
export type ValidatedProperty = {
|
|
293
|
+
key: string;
|
|
282
294
|
required: boolean;
|
|
283
295
|
types: ValidatedPropertyType[];
|
|
284
296
|
validators: ValidatedPropertyValidators;
|
|
285
297
|
};
|
|
286
298
|
|
|
287
|
-
export type ValidatedPropertyType =
|
|
299
|
+
export type ValidatedPropertyType =
|
|
300
|
+
| GenericCallback
|
|
301
|
+
| Schematic<unknown>
|
|
302
|
+
| ValidatedProperty
|
|
303
|
+
| ValueName;
|
|
288
304
|
|
|
289
305
|
export type ValidatedPropertyValidators = {
|
|
290
306
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
291
307
|
};
|
|
292
308
|
|
|
293
|
-
export type ValidatedSchema = {
|
|
294
|
-
keys: {
|
|
295
|
-
array: string[];
|
|
296
|
-
set: Set<string>;
|
|
297
|
-
};
|
|
298
|
-
properties: Record<string, ValidatedProperty>;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
309
|
/**
|
|
302
310
|
* Valid type name strings
|
|
303
311
|
*/
|
package/src/schematic.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
3
|
import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
|
|
4
|
+
import {isSchematic} from './helpers';
|
|
4
5
|
import {
|
|
5
6
|
SchematicError,
|
|
6
7
|
type Infer,
|
|
7
8
|
type Schema,
|
|
8
9
|
type TypedSchema,
|
|
9
|
-
type
|
|
10
|
+
type ValidatedProperty,
|
|
10
11
|
} from './models';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import {getProperties} from './validation/property.validation';
|
|
13
|
+
import {validateObject} from './validation/value.validation';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* A schematic for validating objects
|
|
@@ -17,21 +18,21 @@ import {validateValue} from './validation/value.validation';
|
|
|
17
18
|
export class Schematic<Model> {
|
|
18
19
|
declare private readonly $schematic: true;
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
+
#properties: ValidatedProperty[];
|
|
21
22
|
|
|
22
|
-
constructor(
|
|
23
|
+
constructor(properties: ValidatedProperty[]) {
|
|
23
24
|
Object.defineProperty(this, SCHEMATIC_NAME, {
|
|
24
25
|
value: true,
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
this.#
|
|
28
|
+
this.#properties = properties;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Does the value match the schema?
|
|
32
33
|
*/
|
|
33
34
|
is(value: unknown): value is Model {
|
|
34
|
-
return
|
|
35
|
+
return validateObject(value, this.#properties);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -46,11 +47,13 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
|
|
|
46
47
|
export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
47
48
|
|
|
48
49
|
export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
|
|
50
|
+
if (isSchematic(schema)) {
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
if (!isPlainObject(schema)) {
|
|
50
55
|
throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return new Schematic<Model>(validated);
|
|
58
|
+
return new Schematic<Model>(getProperties(schema));
|
|
56
59
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {join} from '@oscarpalmer/atoms/string/misc';
|
|
4
|
+
import {
|
|
5
|
+
EXPRESSION_INDEX,
|
|
6
|
+
EXPRESSION_PROPERTY,
|
|
7
|
+
MESSAGE_SCHEMA_INVALID_EMPTY,
|
|
8
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED,
|
|
9
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE,
|
|
10
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
|
|
11
|
+
MESSAGE_VALIDATOR_INVALID_KEY,
|
|
12
|
+
MESSAGE_VALIDATOR_INVALID_TYPE,
|
|
13
|
+
MESSAGE_VALIDATOR_INVALID_VALUE,
|
|
14
|
+
PROPERTY_REQUIRED,
|
|
15
|
+
PROPERTY_TYPE,
|
|
16
|
+
PROPERTY_VALIDATORS,
|
|
17
|
+
TEMPLATE_PATTERN,
|
|
18
|
+
TYPE_ALL,
|
|
19
|
+
TYPE_UNDEFINED,
|
|
20
|
+
VALIDATABLE_TYPES,
|
|
21
|
+
} from '../constants';
|
|
22
|
+
import {instanceOf, isSchematic} from '../helpers';
|
|
23
|
+
import {
|
|
24
|
+
SchematicError,
|
|
25
|
+
type ValidatedProperty,
|
|
26
|
+
type ValidatedPropertyType,
|
|
27
|
+
type ValidatedPropertyValidators,
|
|
28
|
+
type ValueName,
|
|
29
|
+
} from '../models';
|
|
30
|
+
|
|
31
|
+
export function getProperties(
|
|
32
|
+
original: PlainObject,
|
|
33
|
+
prefix?: string,
|
|
34
|
+
fromTypes?: boolean,
|
|
35
|
+
): ValidatedProperty[] {
|
|
36
|
+
if (Object.keys(original).length === 0) {
|
|
37
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (PROPERTY_REQUIRED in original && (fromTypes ?? false) && prefix != null) {
|
|
41
|
+
throw new SchematicError(
|
|
42
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED.replace(TEMPLATE_PATTERN, prefix),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const keys = Object.keys(original);
|
|
47
|
+
const keysLength = keys.length;
|
|
48
|
+
|
|
49
|
+
const properties: ValidatedProperty[] = [];
|
|
50
|
+
|
|
51
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
52
|
+
const key = keys[keyIndex];
|
|
53
|
+
|
|
54
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const value = original[key];
|
|
59
|
+
|
|
60
|
+
const types: ValidatedPropertyType[] = [];
|
|
61
|
+
|
|
62
|
+
let required = true;
|
|
63
|
+
let validators: ValidatedPropertyValidators = {};
|
|
64
|
+
|
|
65
|
+
if (isPlainObject(value)) {
|
|
66
|
+
required = getRequired(key, value) ?? required;
|
|
67
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
68
|
+
|
|
69
|
+
if (PROPERTY_TYPE in value) {
|
|
70
|
+
types.push('object', ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
|
|
71
|
+
} else {
|
|
72
|
+
types.push('object', ...getTypes(key, value, prefix));
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
types.push(...getTypes(key, value, prefix));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!required && !types.includes(TYPE_UNDEFINED)) {
|
|
79
|
+
types.push(TYPE_UNDEFINED);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
properties.push({
|
|
83
|
+
key,
|
|
84
|
+
types,
|
|
85
|
+
validators,
|
|
86
|
+
required: required && !types.includes(TYPE_UNDEFINED),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return properties;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getRequired(key: string, obj: PlainObject): boolean | undefined {
|
|
94
|
+
if (!(PROPERTY_REQUIRED in obj)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof obj[PROPERTY_REQUIRED] !== 'boolean') {
|
|
99
|
+
throw new SchematicError(
|
|
100
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE.replace(TEMPLATE_PATTERN, key),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return obj[PROPERTY_REQUIRED];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getTypes(
|
|
108
|
+
key: string,
|
|
109
|
+
original: unknown,
|
|
110
|
+
prefix?: string,
|
|
111
|
+
fromTypes?: boolean,
|
|
112
|
+
): ValidatedPropertyType[] {
|
|
113
|
+
const array = Array.isArray(original) ? original : [original];
|
|
114
|
+
const {length} = array;
|
|
115
|
+
|
|
116
|
+
const types: ValidatedPropertyType[] = [];
|
|
117
|
+
|
|
118
|
+
for (let index = 0; index < length; index += 1) {
|
|
119
|
+
const value = array[index];
|
|
120
|
+
|
|
121
|
+
switch (true) {
|
|
122
|
+
case typeof value === 'function':
|
|
123
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case isPlainObject(value):
|
|
127
|
+
types.push(...getProperties(value, join([prefix, key], '.'), fromTypes));
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case isSchematic(value):
|
|
131
|
+
types.push(value);
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
case TYPE_ALL.has(value as never):
|
|
135
|
+
types.push(value as never);
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
throw new SchematicError(
|
|
140
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (types.length === 0) {
|
|
146
|
+
throw new SchematicError(
|
|
147
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return types;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getValidators(original: unknown): ValidatedPropertyValidators {
|
|
155
|
+
const validators: ValidatedPropertyValidators = {};
|
|
156
|
+
|
|
157
|
+
if (original == null) {
|
|
158
|
+
return validators;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!isPlainObject(original)) {
|
|
162
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const keys = Object.keys(original);
|
|
166
|
+
const {length} = keys;
|
|
167
|
+
|
|
168
|
+
for (let index = 0; index < length; index += 1) {
|
|
169
|
+
const key = keys[index];
|
|
170
|
+
|
|
171
|
+
if (!VALIDATABLE_TYPES.has(key as never)) {
|
|
172
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const value = (original as PlainObject)[key];
|
|
176
|
+
|
|
177
|
+
validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
|
|
178
|
+
if (typeof item !== 'function') {
|
|
179
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return validators;
|
|
187
|
+
}
|
|
@@ -1,74 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {ValidatedProperty, ValidatedPropertyType, ValidatedSchema, Values} from '../models';
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {isSchematic} from '../helpers';
|
|
3
|
+
import type {ValidatedProperty, ValidatedPropertyType, ValueName} from '../models';
|
|
5
4
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
property: ValidatedProperty,
|
|
9
|
-
value: unknown,
|
|
10
|
-
): boolean {
|
|
11
|
-
switch (true) {
|
|
12
|
-
case typeof type === 'function':
|
|
13
|
-
return (type as any)(value);
|
|
14
|
-
|
|
15
|
-
case typeof type === 'string':
|
|
16
|
-
return (
|
|
17
|
-
validators[type](value) &&
|
|
18
|
-
(property.validators[type]?.every(validator => validator(value)) ?? true)
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
default:
|
|
22
|
-
return type.is(value);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function validateValue(validated: ValidatedSchema, obj: unknown): boolean {
|
|
27
|
-
if (typeof obj !== 'object' || obj === null) {
|
|
5
|
+
export function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean {
|
|
6
|
+
if (!isPlainObject(obj)) {
|
|
28
7
|
return false;
|
|
29
8
|
}
|
|
30
9
|
|
|
31
|
-
const
|
|
32
|
-
const keysLength = keys.array.length;
|
|
10
|
+
const propertiesLength = properties.length;
|
|
33
11
|
|
|
34
|
-
|
|
12
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
13
|
+
const property = properties[propertyIndex];
|
|
35
14
|
|
|
36
|
-
|
|
15
|
+
const {key, required, types} = property;
|
|
37
16
|
|
|
38
|
-
|
|
39
|
-
const key = keys.array[keyIndex];
|
|
17
|
+
const value = obj[key];
|
|
40
18
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (ignore.has(prefix)) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const property = properties[key];
|
|
48
|
-
const value = smushed[key];
|
|
49
|
-
|
|
50
|
-
if (value === undefined && property.required && !property.types.includes(TYPE_UNDEFINED)) {
|
|
19
|
+
if (value === undefined && required) {
|
|
51
20
|
return false;
|
|
52
21
|
}
|
|
53
22
|
|
|
54
|
-
const typesLength =
|
|
55
|
-
|
|
56
|
-
if (typesLength === 1) {
|
|
57
|
-
if (!validateType(property.types[0], property, value)) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
23
|
+
const typesLength = types.length;
|
|
63
24
|
|
|
64
25
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
65
|
-
const type =
|
|
66
|
-
|
|
67
|
-
if (validateType(type, property, value)) {
|
|
68
|
-
if ((type as never) !== 'object') {
|
|
69
|
-
ignore.add(key);
|
|
70
|
-
}
|
|
26
|
+
const type = types[typeIndex];
|
|
71
27
|
|
|
28
|
+
if (validateValue(type, property, value)) {
|
|
72
29
|
continue outer;
|
|
73
30
|
}
|
|
74
31
|
}
|
|
@@ -79,20 +36,39 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
|
|
|
79
36
|
return true;
|
|
80
37
|
}
|
|
81
38
|
|
|
82
|
-
|
|
39
|
+
function validateValue(
|
|
40
|
+
type: ValidatedPropertyType,
|
|
41
|
+
property: ValidatedProperty,
|
|
42
|
+
value: unknown,
|
|
43
|
+
): boolean {
|
|
44
|
+
switch (true) {
|
|
45
|
+
case isSchematic(type):
|
|
46
|
+
return type.is(value);
|
|
47
|
+
|
|
48
|
+
case typeof type === 'function':
|
|
49
|
+
return (type as (value: unknown) => boolean)(value);
|
|
83
50
|
|
|
84
|
-
|
|
51
|
+
case typeof type === 'object':
|
|
52
|
+
return validateObject(value, [type] as ValidatedProperty[]);
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
return (
|
|
56
|
+
validators[type as ValueName](value) &&
|
|
57
|
+
(property.validators[type as ValueName]?.every(validator => validator(value)) ?? true)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
85
61
|
|
|
86
62
|
//
|
|
87
63
|
|
|
88
|
-
const validators: Record<
|
|
64
|
+
const validators: Record<ValueName, (value: unknown) => boolean> = {
|
|
89
65
|
array: Array.isArray,
|
|
90
66
|
bigint: value => typeof value === 'bigint',
|
|
91
67
|
boolean: value => typeof value === 'boolean',
|
|
92
68
|
date: value => value instanceof Date,
|
|
93
69
|
function: value => typeof value === 'function',
|
|
94
70
|
null: value => value === null,
|
|
95
|
-
number: value => typeof value === 'number'
|
|
71
|
+
number: value => typeof value === 'number',
|
|
96
72
|
object: value => typeof value === 'object' && value !== null,
|
|
97
73
|
string: value => typeof value === 'string',
|
|
98
74
|
symbol: value => typeof value === 'symbol',
|
package/types/constants.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export declare const ERROR_NAME = "SchematicError";
|
|
2
|
-
export declare const EXPRESSION_HAS_NUMBER: RegExp;
|
|
3
2
|
export declare const EXPRESSION_INDEX: RegExp;
|
|
3
|
+
export declare const EXPRESSION_KEY_PREFIX: RegExp;
|
|
4
|
+
export declare const EXPRESSION_KEY_VALUE: RegExp;
|
|
4
5
|
export declare const EXPRESSION_PROPERTY: RegExp;
|
|
5
6
|
export declare const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
6
7
|
export declare const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
7
|
-
export declare const
|
|
8
|
+
export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED = "'<>.$required' property is not allowed for schemas in $type";
|
|
9
|
+
export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE = "'<>.$required' property must be a boolean";
|
|
8
10
|
export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
9
11
|
export declare const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
10
12
|
export declare const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Constructor } from './models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
|
|
4
4
|
export declare function isSchematic(value: unknown): value is Schematic<never>;
|
package/types/index.d.ts
CHANGED
package/types/models.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlainObject, Simplify } from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type { GenericCallback, PlainObject, Simplify } from '@oscarpalmer/atoms/models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
3
|
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
4
4
|
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
@@ -50,9 +50,9 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
50
50
|
* A schema for validating objects
|
|
51
51
|
*/
|
|
52
52
|
export type Schema = SchemaIndex;
|
|
53
|
-
type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName |
|
|
53
|
+
type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
54
54
|
interface SchemaIndex {
|
|
55
|
-
[key: string]: SchemaEntry | SchemaEntry[];
|
|
55
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* A property definition with explicit type(s) and optional requirement flag
|
|
@@ -62,7 +62,7 @@ export type SchemaProperty = {
|
|
|
62
62
|
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
63
63
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
64
64
|
};
|
|
65
|
-
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
65
|
+
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
66
66
|
export declare class SchematicError extends Error {
|
|
67
67
|
constructor(message: string);
|
|
68
68
|
}
|
|
@@ -123,21 +123,15 @@ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => voi
|
|
|
123
123
|
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
124
124
|
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
|
|
125
125
|
export type ValidatedProperty = {
|
|
126
|
+
key: string;
|
|
126
127
|
required: boolean;
|
|
127
128
|
types: ValidatedPropertyType[];
|
|
128
129
|
validators: ValidatedPropertyValidators;
|
|
129
130
|
};
|
|
130
|
-
export type ValidatedPropertyType = Schematic<unknown> | ValueName;
|
|
131
|
+
export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
|
|
131
132
|
export type ValidatedPropertyValidators = {
|
|
132
133
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
133
134
|
};
|
|
134
|
-
export type ValidatedSchema = {
|
|
135
|
-
keys: {
|
|
136
|
-
array: string[];
|
|
137
|
-
set: Set<string>;
|
|
138
|
-
};
|
|
139
|
-
properties: Record<string, ValidatedProperty>;
|
|
140
|
-
};
|
|
141
135
|
/**
|
|
142
136
|
* Valid type name strings
|
|
143
137
|
*/
|
package/types/schematic.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { PlainObject } from '@oscarpalmer/atoms/models';
|
|
2
|
-
import { type Infer, type Schema, type TypedSchema, type
|
|
2
|
+
import { type Infer, type Schema, type TypedSchema, type ValidatedProperty } from './models';
|
|
3
3
|
/**
|
|
4
4
|
* A schematic for validating objects
|
|
5
5
|
*/
|
|
6
6
|
export declare class Schematic<Model> {
|
|
7
7
|
#private;
|
|
8
8
|
private readonly $schematic;
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(properties: ValidatedProperty[]);
|
|
10
10
|
/**
|
|
11
11
|
* Does the value match the schema?
|
|
12
12
|
*/
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { ValidatedProperty
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function validateValue(validated: ValidatedSchema, obj: unknown): boolean;
|
|
1
|
+
import type { ValidatedProperty } from '../models';
|
|
2
|
+
export declare function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, MESSAGE_SCHEMA_INVALID_EMPTY, 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, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.js";
|
|
2
|
-
import { isInstance, isSchematic } from "../is.js";
|
|
3
|
-
import { SchematicError } from "../models.js";
|
|
4
|
-
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
5
|
-
import { smush } from "@oscarpalmer/atoms/value/misc";
|
|
6
|
-
function addPropertyType(to, key, values, validators, required) {
|
|
7
|
-
if (to.keys.set.has(key)) {
|
|
8
|
-
const property = to.properties[key];
|
|
9
|
-
for (const type of values) property.types.push(type);
|
|
10
|
-
} else {
|
|
11
|
-
to.keys.array.push(key);
|
|
12
|
-
to.keys.set.add(key);
|
|
13
|
-
to.properties[key] = {
|
|
14
|
-
required,
|
|
15
|
-
types: values,
|
|
16
|
-
validators: {}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
if (!required && !to.properties[key].types.includes("undefined")) to.properties[key].types.push(TYPE_UNDEFINED);
|
|
20
|
-
to.properties[key].validators = validators;
|
|
21
|
-
}
|
|
22
|
-
function getSchema(schema) {
|
|
23
|
-
return getValidatedSchema(schema, {
|
|
24
|
-
keys: {
|
|
25
|
-
array: [],
|
|
26
|
-
set: /* @__PURE__ */ new Set()
|
|
27
|
-
},
|
|
28
|
-
properties: {}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function getTypes(value, validated, prefix) {
|
|
32
|
-
const propertyTypes = [];
|
|
33
|
-
const values = Array.isArray(value) ? value : [value];
|
|
34
|
-
const { length } = values;
|
|
35
|
-
for (let index = 0; index < length; index += 1) {
|
|
36
|
-
const type = values[index];
|
|
37
|
-
if (isSchematic(type) || TYPE_ALL.has(type)) {
|
|
38
|
-
propertyTypes.push(type);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (typeof type === "function") {
|
|
42
|
-
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", prefix));
|
|
46
|
-
if ("$type" in type) {
|
|
47
|
-
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const { [PROPERTY_REQUIRED]: required, ...nested } = type;
|
|
51
|
-
if ("$required" in type && typeof required !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", prefix));
|
|
52
|
-
addPropertyType(validated, prefix, [TYPE_OBJECT], {}, required !== false);
|
|
53
|
-
propertyTypes.push(TYPE_OBJECT);
|
|
54
|
-
getValidatedSchema(nested, validated, prefix);
|
|
55
|
-
}
|
|
56
|
-
return propertyTypes;
|
|
57
|
-
}
|
|
58
|
-
function getValidatedSchema(schema, validated, prefix) {
|
|
59
|
-
const smushed = smush(schema);
|
|
60
|
-
const keys = Object.keys(smushed);
|
|
61
|
-
const { length } = keys;
|
|
62
|
-
const arrayKeys = /* @__PURE__ */ new Set();
|
|
63
|
-
const noPrefix = prefix == null;
|
|
64
|
-
prefix = noPrefix ? "" : `${prefix}.`;
|
|
65
|
-
for (let index = 0; index < length; index += 1) {
|
|
66
|
-
const key = keys[index];
|
|
67
|
-
const value = smushed[key];
|
|
68
|
-
if (Array.isArray(value)) arrayKeys.add(key);
|
|
69
|
-
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
70
|
-
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
71
|
-
let required = true;
|
|
72
|
-
let validators = {};
|
|
73
|
-
const isObject = isPlainObject(value);
|
|
74
|
-
if (isObject && "$required" in value) {
|
|
75
|
-
if (typeof value["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
76
|
-
required = value[PROPERTY_REQUIRED] === true;
|
|
77
|
-
}
|
|
78
|
-
if (isObject && "$validators" in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
79
|
-
const prefixedKey = `${prefix}${key}`;
|
|
80
|
-
const types = getTypes(value, validated, prefixedKey);
|
|
81
|
-
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
|
|
82
|
-
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
83
|
-
}
|
|
84
|
-
if (noPrefix) validated.keys.array.sort();
|
|
85
|
-
if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
86
|
-
return validated;
|
|
87
|
-
}
|
|
88
|
-
function getValidators(original) {
|
|
89
|
-
const validators = {};
|
|
90
|
-
if (original == null) return validators;
|
|
91
|
-
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
92
|
-
const keys = Object.keys(original);
|
|
93
|
-
const { length } = keys;
|
|
94
|
-
for (let index = 0; index < length; index += 1) {
|
|
95
|
-
const key = keys[index];
|
|
96
|
-
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
97
|
-
const value = original[key];
|
|
98
|
-
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
99
|
-
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
100
|
-
return true;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return validators;
|
|
104
|
-
}
|
|
105
|
-
export { getSchema };
|