@oscarpalmer/jhunal 0.7.0 → 0.9.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 +4 -3
- package/dist/index.js +2 -1
- package/dist/is.js +8 -6
- package/dist/jhunal.full.js +109 -73
- package/dist/schematic.js +12 -7
- package/dist/validation/schema.validation.js +24 -17
- package/dist/validation/value.validation.js +6 -5
- package/package.json +10 -10
- package/src/constants.ts +6 -2
- package/src/index.ts +1 -0
- package/src/is.ts +10 -8
- package/src/models.ts +112 -54
- package/src/schematic.ts +6 -7
- package/src/validation/schema.validation.ts +31 -19
- package/src/validation/value.validation.ts +12 -6
- package/types/constants.d.ts +3 -0
- package/types/index.d.ts +1 -0
- package/types/is.d.ts +2 -1
- package/types/models.d.ts +32 -24
- package/types/schematic.d.ts +1 -1
- package/types/validation/schema.validation.d.ts +1 -1
package/dist/constants.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
2
|
+
const EXPRESSION_INDEX = /\.\d+$/;
|
|
3
|
+
const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
|
|
1
4
|
const PROPERTY_REQUIRED = "$required";
|
|
2
5
|
const PROPERTY_TYPE = "$type";
|
|
3
6
|
const SCHEMATIC_NAME = "$schematic";
|
|
@@ -8,14 +11,12 @@ const TYPE_ALL = new Set([
|
|
|
8
11
|
"bigint",
|
|
9
12
|
"boolean",
|
|
10
13
|
"date",
|
|
11
|
-
"date-like",
|
|
12
14
|
"function",
|
|
13
15
|
"null",
|
|
14
16
|
"number",
|
|
15
|
-
"numerical",
|
|
16
17
|
"string",
|
|
17
18
|
"symbol",
|
|
18
19
|
TYPE_OBJECT,
|
|
19
20
|
TYPE_UNDEFINED
|
|
20
21
|
]);
|
|
21
|
-
export { PROPERTY_REQUIRED, PROPERTY_TYPE, SCHEMATIC_NAME, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED };
|
|
22
|
+
export { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, SCHEMATIC_NAME, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED };
|
package/dist/index.js
CHANGED
package/dist/is.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (
|
|
5
|
-
return
|
|
1
|
+
import "./constants.js";
|
|
2
|
+
import { isConstructor } from "@oscarpalmer/atoms/is";
|
|
3
|
+
function isInstance(constructor) {
|
|
4
|
+
if (!isConstructor(constructor)) throw new TypeError("Expected a constructor function");
|
|
5
|
+
return (value) => {
|
|
6
|
+
return value instanceof constructor;
|
|
7
|
+
};
|
|
6
8
|
}
|
|
7
9
|
function isSchematic(value) {
|
|
8
10
|
return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
|
|
9
11
|
}
|
|
10
|
-
export {
|
|
12
|
+
export { isInstance, isSchematic };
|
package/dist/jhunal.full.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Is the value an array or a record?
|
|
3
|
+
* @param value Value to check
|
|
4
|
+
* @returns `true` if the value is an array or a record, otherwise `false`
|
|
5
|
+
*/
|
|
6
|
+
function isArrayOrPlainObject(value) {
|
|
7
|
+
return Array.isArray(value) || isPlainObject(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Is the value a constructor function?
|
|
11
|
+
* @param value Value to check
|
|
12
|
+
* @returns `true` if the value is a constructor function, otherwise `false`
|
|
13
|
+
*/
|
|
14
|
+
function isConstructor(value) {
|
|
15
|
+
return typeof value === "function" && value.prototype?.constructor === value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Is the value a plain object?
|
|
19
|
+
* @param value Value to check
|
|
20
|
+
* @returns `true` if the value is a plain object, otherwise `false`
|
|
21
|
+
*/
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
if (value === null || typeof value !== "object") return false;
|
|
24
|
+
if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
|
|
25
|
+
const prototype = Object.getPrototypeOf(value);
|
|
26
|
+
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
27
|
+
}
|
|
21
28
|
function compact(array, strict) {
|
|
22
29
|
if (!Array.isArray(array)) return [];
|
|
23
30
|
if (strict === true) return array.filter(Boolean);
|
|
@@ -29,6 +36,11 @@ function compact(array, strict) {
|
|
|
29
36
|
}
|
|
30
37
|
return compacted;
|
|
31
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the string value from any value
|
|
41
|
+
* @param value Original value
|
|
42
|
+
* @returns String representation of the value
|
|
43
|
+
*/
|
|
32
44
|
function getString(value) {
|
|
33
45
|
if (typeof value === "string") return value;
|
|
34
46
|
if (value == null) return "";
|
|
@@ -37,31 +49,45 @@ function getString(value) {
|
|
|
37
49
|
const asString = String(value.valueOf?.() ?? value);
|
|
38
50
|
return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
|
|
39
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Join an array of values into a string
|
|
54
|
+
* @param value Array of values
|
|
55
|
+
* @param delimiter Delimiter to use between values
|
|
56
|
+
* @returns Joined string
|
|
57
|
+
*/
|
|
40
58
|
function join(value, delimiter) {
|
|
41
59
|
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
42
60
|
}
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
62
|
+
const EXPRESSION_INDEX = /\.\d+$/;
|
|
63
|
+
const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
|
|
64
|
+
const PROPERTY_REQUIRED = "$required";
|
|
65
|
+
const PROPERTY_TYPE = "$type";
|
|
66
|
+
const SCHEMATIC_NAME = "$schematic";
|
|
67
|
+
const TYPE_OBJECT = "object";
|
|
68
|
+
const TYPE_UNDEFINED = "undefined";
|
|
69
|
+
const TYPE_ALL = new Set([
|
|
70
|
+
"array",
|
|
71
|
+
"bigint",
|
|
72
|
+
"boolean",
|
|
73
|
+
"date",
|
|
74
|
+
"function",
|
|
75
|
+
"null",
|
|
76
|
+
"number",
|
|
77
|
+
"string",
|
|
78
|
+
"symbol",
|
|
79
|
+
TYPE_OBJECT,
|
|
80
|
+
TYPE_UNDEFINED
|
|
81
|
+
]);
|
|
82
|
+
function isInstance(constructor) {
|
|
83
|
+
if (!isConstructor(constructor)) throw new TypeError("Expected a constructor function");
|
|
84
|
+
return (value) => {
|
|
85
|
+
return value instanceof constructor;
|
|
86
|
+
};
|
|
45
87
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
|
|
49
|
-
const prototype = Object.getPrototypeOf(value);
|
|
50
|
-
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
88
|
+
function isSchematic(value) {
|
|
89
|
+
return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
|
|
51
90
|
}
|
|
52
|
-
new Set([
|
|
53
|
-
Int8Array,
|
|
54
|
-
Uint8Array,
|
|
55
|
-
Uint8ClampedArray,
|
|
56
|
-
Int16Array,
|
|
57
|
-
Uint16Array,
|
|
58
|
-
Int32Array,
|
|
59
|
-
Uint32Array,
|
|
60
|
-
Float32Array,
|
|
61
|
-
Float64Array,
|
|
62
|
-
BigInt64Array,
|
|
63
|
-
BigUint64Array
|
|
64
|
-
]);
|
|
65
91
|
function flattenObject(value, depth, smushed, prefix) {
|
|
66
92
|
if (depth >= MAX_DEPTH) return {};
|
|
67
93
|
if (smushed.has(value)) return smushed.get(value);
|
|
@@ -86,18 +112,15 @@ function flattenObject(value, depth, smushed, prefix) {
|
|
|
86
112
|
smushed.set(value, flattened);
|
|
87
113
|
return flattened;
|
|
88
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Smush an object into a flat object that uses dot notation keys
|
|
117
|
+
* @param value Object to smush
|
|
118
|
+
* @returns Smushed object with dot notation keys
|
|
119
|
+
*/
|
|
89
120
|
function smush(value) {
|
|
90
121
|
return typeof value === "object" && value !== null ? flattenObject(value, 0, /* @__PURE__ */ new WeakMap()) : {};
|
|
91
122
|
}
|
|
92
123
|
var MAX_DEPTH = 100;
|
|
93
|
-
function isDateLike(value) {
|
|
94
|
-
if (value instanceof Date) return true;
|
|
95
|
-
if (typeof value === "number") return value >= -864e13 && value <= 864e13;
|
|
96
|
-
return typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
97
|
-
}
|
|
98
|
-
function isSchematic(value) {
|
|
99
|
-
return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
|
|
100
|
-
}
|
|
101
124
|
function addPropertyType(to, key, values, required) {
|
|
102
125
|
if (to.keys.set.has(key)) {
|
|
103
126
|
const property = to.properties[key];
|
|
@@ -112,17 +135,33 @@ function addPropertyType(to, key, values, required) {
|
|
|
112
135
|
}
|
|
113
136
|
if (!required && !to.properties[key].types.includes(TYPE_UNDEFINED)) to.properties[key].types.push(TYPE_UNDEFINED);
|
|
114
137
|
}
|
|
138
|
+
function getSchema(schema) {
|
|
139
|
+
const validated = {
|
|
140
|
+
enabled: false,
|
|
141
|
+
keys: {
|
|
142
|
+
array: [],
|
|
143
|
+
set: /* @__PURE__ */ new Set()
|
|
144
|
+
},
|
|
145
|
+
properties: {}
|
|
146
|
+
};
|
|
147
|
+
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
148
|
+
}
|
|
115
149
|
function getTypes(value, validated, prefix) {
|
|
116
150
|
const propertyTypes = [];
|
|
117
151
|
const values = Array.isArray(value) ? value : [value];
|
|
118
152
|
const { length } = values;
|
|
119
153
|
for (let index = 0; index < length; index += 1) {
|
|
120
154
|
const type = values[index];
|
|
121
|
-
|
|
155
|
+
const typeOfType = typeof type;
|
|
156
|
+
if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
|
|
122
157
|
propertyTypes.push(type);
|
|
123
158
|
continue;
|
|
124
159
|
}
|
|
125
|
-
if (
|
|
160
|
+
if (typeOfType === "function") {
|
|
161
|
+
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (typeOfType !== "object" || type === null) continue;
|
|
126
165
|
if (PROPERTY_TYPE in type) {
|
|
127
166
|
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
128
167
|
continue;
|
|
@@ -144,8 +183,8 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
144
183
|
const key = keys[index];
|
|
145
184
|
const value = smushed[key];
|
|
146
185
|
if (Array.isArray(value)) arrayKeys.add(key);
|
|
147
|
-
if (
|
|
148
|
-
if (
|
|
186
|
+
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
187
|
+
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
149
188
|
let required = true;
|
|
150
189
|
if (typeof value === "object" && value !== null && PROPERTY_REQUIRED in value) required = typeof value[PROPERTY_REQUIRED] === "boolean" ? value[PROPERTY_REQUIRED] : true;
|
|
151
190
|
const prefixedKey = `${prefix}${key}`;
|
|
@@ -155,18 +194,12 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
155
194
|
if (noPrefix) validated.keys.array.sort();
|
|
156
195
|
return validated;
|
|
157
196
|
}
|
|
158
|
-
function validateSchema(schema) {
|
|
159
|
-
const validated = {
|
|
160
|
-
keys: {
|
|
161
|
-
array: [],
|
|
162
|
-
set: /* @__PURE__ */ new Set()
|
|
163
|
-
},
|
|
164
|
-
properties: {}
|
|
165
|
-
};
|
|
166
|
-
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
167
|
-
}
|
|
168
197
|
function validateType(type, value) {
|
|
169
|
-
|
|
198
|
+
switch (true) {
|
|
199
|
+
case typeof type === "function": return type(value);
|
|
200
|
+
case typeof type === "string": return validators[type](value);
|
|
201
|
+
default: return type.is(value);
|
|
202
|
+
}
|
|
170
203
|
}
|
|
171
204
|
function validateValue(validated, obj) {
|
|
172
205
|
if (typeof obj !== "object" || obj === null) return false;
|
|
@@ -189,7 +222,7 @@ function validateValue(validated, obj) {
|
|
|
189
222
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
190
223
|
const type = property.types[typeIndex];
|
|
191
224
|
if (validateType(type, value)) {
|
|
192
|
-
if (type !==
|
|
225
|
+
if (type !== "object") ignore.add(key);
|
|
193
226
|
continue outer;
|
|
194
227
|
}
|
|
195
228
|
}
|
|
@@ -203,32 +236,35 @@ const validators = {
|
|
|
203
236
|
bigint: (value) => typeof value === "bigint",
|
|
204
237
|
boolean: (value) => typeof value === "boolean",
|
|
205
238
|
date: (value) => value instanceof Date,
|
|
206
|
-
"date-like": isDateLike,
|
|
207
239
|
function: (value) => typeof value === "function",
|
|
208
240
|
null: (value) => value === null,
|
|
209
241
|
number: (value) => typeof value === "number" && !Number.isNaN(value),
|
|
210
|
-
numerical: (value) => validators.bigint(value) || validators.number(value),
|
|
211
242
|
object: (value) => typeof value === "object" && value !== null,
|
|
212
243
|
string: (value) => typeof value === "string",
|
|
213
244
|
symbol: (value) => typeof value === "symbol",
|
|
214
245
|
undefined: (value) => value === void 0
|
|
215
246
|
};
|
|
247
|
+
/**
|
|
248
|
+
* A schematic for validating objects
|
|
249
|
+
*/
|
|
216
250
|
var Schematic = class {
|
|
217
251
|
#schema;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return this.#validatable;
|
|
252
|
+
get enabled() {
|
|
253
|
+
return this.#schema.enabled;
|
|
221
254
|
}
|
|
222
255
|
constructor(schema) {
|
|
223
256
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
224
|
-
this.#schema =
|
|
225
|
-
this.#
|
|
257
|
+
this.#schema = getSchema(schema);
|
|
258
|
+
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
226
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Does the value match the schema?
|
|
262
|
+
*/
|
|
227
263
|
is(value) {
|
|
228
|
-
return this.#
|
|
264
|
+
return this.#schema.enabled && validateValue(this.#schema, value);
|
|
229
265
|
}
|
|
230
266
|
};
|
|
231
267
|
function schematic(schema) {
|
|
232
268
|
return new Schematic(schema);
|
|
233
269
|
}
|
|
234
|
-
export { schematic };
|
|
270
|
+
export { isInstance, schematic };
|
package/dist/schematic.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { SCHEMATIC_NAME } from "./constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getSchema } from "./validation/schema.validation.js";
|
|
3
3
|
import { validateValue } from "./validation/value.validation.js";
|
|
4
|
+
/**
|
|
5
|
+
* A schematic for validating objects
|
|
6
|
+
*/
|
|
4
7
|
var Schematic = class {
|
|
5
8
|
#schema;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return this.#validatable;
|
|
9
|
+
get enabled() {
|
|
10
|
+
return this.#schema.enabled;
|
|
9
11
|
}
|
|
10
12
|
constructor(schema) {
|
|
11
13
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
12
|
-
this.#schema =
|
|
13
|
-
this.#
|
|
14
|
+
this.#schema = getSchema(schema);
|
|
15
|
+
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Does the value match the schema?
|
|
19
|
+
*/
|
|
15
20
|
is(value) {
|
|
16
|
-
return this.#
|
|
21
|
+
return this.#schema.enabled && validateValue(this.#schema, value);
|
|
17
22
|
}
|
|
18
23
|
};
|
|
19
24
|
function schematic(schema) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { PROPERTY_REQUIRED, PROPERTY_TYPE, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
|
|
2
|
-
import { isSchematic } from "../is.js";
|
|
1
|
+
import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
|
|
2
|
+
import { isInstance, isSchematic } from "../is.js";
|
|
3
|
+
import { isConstructor } from "@oscarpalmer/atoms/is";
|
|
3
4
|
import { smush } from "@oscarpalmer/atoms/value";
|
|
4
5
|
function addPropertyType(to, key, values, required) {
|
|
5
6
|
if (to.keys.set.has(key)) {
|
|
@@ -15,17 +16,33 @@ function addPropertyType(to, key, values, required) {
|
|
|
15
16
|
}
|
|
16
17
|
if (!required && !to.properties[key].types.includes("undefined")) to.properties[key].types.push(TYPE_UNDEFINED);
|
|
17
18
|
}
|
|
19
|
+
function getSchema(schema) {
|
|
20
|
+
const validated = {
|
|
21
|
+
enabled: false,
|
|
22
|
+
keys: {
|
|
23
|
+
array: [],
|
|
24
|
+
set: /* @__PURE__ */ new Set()
|
|
25
|
+
},
|
|
26
|
+
properties: {}
|
|
27
|
+
};
|
|
28
|
+
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
29
|
+
}
|
|
18
30
|
function getTypes(value, validated, prefix) {
|
|
19
31
|
const propertyTypes = [];
|
|
20
32
|
const values = Array.isArray(value) ? value : [value];
|
|
21
33
|
const { length } = values;
|
|
22
34
|
for (let index = 0; index < length; index += 1) {
|
|
23
35
|
const type = values[index];
|
|
24
|
-
|
|
36
|
+
const typeOfType = typeof type;
|
|
37
|
+
if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
|
|
25
38
|
propertyTypes.push(type);
|
|
26
39
|
continue;
|
|
27
40
|
}
|
|
28
|
-
if (
|
|
41
|
+
if (typeOfType === "function") {
|
|
42
|
+
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeOfType !== "object" || type === null) continue;
|
|
29
46
|
if ("$type" in type) {
|
|
30
47
|
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
31
48
|
continue;
|
|
@@ -47,8 +64,8 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
47
64
|
const key = keys[index];
|
|
48
65
|
const value = smushed[key];
|
|
49
66
|
if (Array.isArray(value)) arrayKeys.add(key);
|
|
50
|
-
if (
|
|
51
|
-
if (
|
|
67
|
+
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
68
|
+
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
52
69
|
let required = true;
|
|
53
70
|
if (typeof value === "object" && value !== null && "$required" in value) required = typeof value["$required"] === "boolean" ? value[PROPERTY_REQUIRED] : true;
|
|
54
71
|
const prefixedKey = `${prefix}${key}`;
|
|
@@ -58,14 +75,4 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
58
75
|
if (noPrefix) validated.keys.array.sort();
|
|
59
76
|
return validated;
|
|
60
77
|
}
|
|
61
|
-
|
|
62
|
-
const validated = {
|
|
63
|
-
keys: {
|
|
64
|
-
array: [],
|
|
65
|
-
set: /* @__PURE__ */ new Set()
|
|
66
|
-
},
|
|
67
|
-
properties: {}
|
|
68
|
-
};
|
|
69
|
-
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
70
|
-
}
|
|
71
|
-
export { validateSchema };
|
|
78
|
+
export { getSchema };
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isDateLike } from "../is.js";
|
|
1
|
+
import "../constants.js";
|
|
3
2
|
import { smush } from "@oscarpalmer/atoms/value";
|
|
4
3
|
function validateType(type, value) {
|
|
5
|
-
|
|
4
|
+
switch (true) {
|
|
5
|
+
case typeof type === "function": return type(value);
|
|
6
|
+
case typeof type === "string": return validators[type](value);
|
|
7
|
+
default: return type.is(value);
|
|
8
|
+
}
|
|
6
9
|
}
|
|
7
10
|
function validateValue(validated, obj) {
|
|
8
11
|
if (typeof obj !== "object" || obj === null) return false;
|
|
@@ -39,11 +42,9 @@ var validators = {
|
|
|
39
42
|
bigint: (value) => typeof value === "bigint",
|
|
40
43
|
boolean: (value) => typeof value === "boolean",
|
|
41
44
|
date: (value) => value instanceof Date,
|
|
42
|
-
"date-like": isDateLike,
|
|
43
45
|
function: (value) => typeof value === "function",
|
|
44
46
|
null: (value) => value === null,
|
|
45
47
|
number: (value) => typeof value === "number" && !Number.isNaN(value),
|
|
46
|
-
numerical: (value) => validators.bigint(value) || validators.number(value),
|
|
47
48
|
object: (value) => typeof value === "object" && value !== null,
|
|
48
49
|
string: (value) => typeof value === "string",
|
|
49
50
|
symbol: (value) => typeof value === "symbol",
|
package/package.json
CHANGED
|
@@ -4,26 +4,26 @@
|
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@oscarpalmer/atoms": "^0.
|
|
7
|
+
"@oscarpalmer/atoms": "^0.139"
|
|
8
8
|
},
|
|
9
9
|
"description": "Flies free beneath the glistening moons…",
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@types/node": "^25",
|
|
11
|
+
"@types/node": "^25.3",
|
|
12
12
|
"@vitest/coverage-istanbul": "^4",
|
|
13
|
-
"jsdom": "^
|
|
14
|
-
"oxfmt": "^0.
|
|
15
|
-
"oxlint": "^1.
|
|
16
|
-
"rolldown": "1.0.0-
|
|
13
|
+
"jsdom": "^28.1",
|
|
14
|
+
"oxfmt": "^0.34",
|
|
15
|
+
"oxlint": "^1.49",
|
|
16
|
+
"rolldown": "1.0.0-rc.5",
|
|
17
17
|
"tslib": "^2.8",
|
|
18
18
|
"typescript": "^5.9",
|
|
19
|
-
"vite": "8.0.0-beta.
|
|
19
|
+
"vite": "8.0.0-beta.15",
|
|
20
20
|
"vitest": "^4"
|
|
21
21
|
},
|
|
22
22
|
"exports": {
|
|
23
23
|
"./package.json": "./package.json",
|
|
24
24
|
".": {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
"types": "./types/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"files": ["dist", "src", "types"],
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
},
|
|
46
46
|
"type": "module",
|
|
47
47
|
"types": "./types/index.d.ts",
|
|
48
|
-
"version": "0.
|
|
48
|
+
"version": "0.9.0"
|
|
49
49
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type {Values} from './models';
|
|
2
2
|
|
|
3
|
+
export const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
4
|
+
|
|
5
|
+
export const EXPRESSION_INDEX = /\.\d+$/;
|
|
6
|
+
|
|
7
|
+
export const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
|
|
8
|
+
|
|
3
9
|
export const PROPERTY_REQUIRED = '$required';
|
|
4
10
|
|
|
5
11
|
export const PROPERTY_TYPE = '$type';
|
|
@@ -15,11 +21,9 @@ export const TYPE_ALL = new Set<keyof Values>([
|
|
|
15
21
|
'bigint',
|
|
16
22
|
'boolean',
|
|
17
23
|
'date',
|
|
18
|
-
'date-like',
|
|
19
24
|
'function',
|
|
20
25
|
'null',
|
|
21
26
|
'number',
|
|
22
|
-
'numerical',
|
|
23
27
|
'string',
|
|
24
28
|
'symbol',
|
|
25
29
|
TYPE_OBJECT,
|
package/src/index.ts
CHANGED
package/src/is.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
1
2
|
import {SCHEMATIC_NAME} from './constants';
|
|
3
|
+
import type {Constructor} from './models';
|
|
2
4
|
import type {Schematic} from './schematic';
|
|
3
5
|
|
|
4
|
-
export function
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export function isInstance<Instance>(
|
|
7
|
+
constructor: Constructor<Instance>,
|
|
8
|
+
): (value: unknown) => value is Instance {
|
|
9
|
+
if (!isConstructor(constructor)) {
|
|
10
|
+
throw new TypeError('Expected a constructor function');
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
return value
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return typeof value === 'string' && !Number.isNaN(Date.parse(value));
|
|
13
|
+
return (value: unknown): value is Instance => {
|
|
14
|
+
return value instanceof constructor;
|
|
15
|
+
};
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function isSchematic(value: unknown): value is Schematic<never> {
|
package/src/models.ts
CHANGED
|
@@ -1,58 +1,67 @@
|
|
|
1
1
|
import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import type {Schematic} from './schematic';
|
|
3
3
|
|
|
4
|
+
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
5
|
+
|
|
6
|
+
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
7
|
+
infer Head,
|
|
8
|
+
...infer Tail,
|
|
9
|
+
]
|
|
10
|
+
? Head extends Seen[number]
|
|
11
|
+
? DeduplicateTuple<Tail, Seen>
|
|
12
|
+
: DeduplicateTuple<Tail, [...Seen, Head]>
|
|
13
|
+
: Seen;
|
|
14
|
+
|
|
4
15
|
/**
|
|
5
16
|
* Infer the TypeScript type from a schema definition
|
|
6
17
|
*/
|
|
7
18
|
export type Infer<Model extends Schema> = Simplify<
|
|
8
19
|
{
|
|
9
|
-
[Key in InferRequiredKeys<Model>]: Model[Key]
|
|
10
|
-
? SchematicModel
|
|
11
|
-
: Model[Key] extends SchemaProperty
|
|
12
|
-
? InferPropertyValue<Model[Key]>
|
|
13
|
-
: InferEntryValue<Model[Key]>;
|
|
20
|
+
[Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
|
|
14
21
|
} & {
|
|
15
|
-
[Key in InferOptionalKeys<Model>]?: Model[Key]
|
|
16
|
-
? SchematicModel
|
|
17
|
-
: Model[Key] extends SchemaProperty
|
|
18
|
-
? InferPropertyValue<Model[Key]>
|
|
19
|
-
: never;
|
|
22
|
+
[Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
|
|
20
23
|
}
|
|
21
24
|
>;
|
|
22
25
|
|
|
23
|
-
type InferEntryValue<Value> = Value extends ValueName
|
|
24
|
-
? Values[Value]
|
|
25
|
-
: Value extends ValueName[]
|
|
26
|
-
? Values[Value[number]]
|
|
27
|
-
: never;
|
|
28
|
-
|
|
29
26
|
type InferOptionalKeys<Model extends Schema> = keyof {
|
|
30
27
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
|
|
31
28
|
};
|
|
32
29
|
|
|
33
30
|
type InferPropertyType<Value> = Value extends (infer Item)[]
|
|
34
|
-
?
|
|
35
|
-
:
|
|
31
|
+
? InferPropertyValue<Item>
|
|
32
|
+
: InferPropertyValue<Value>;
|
|
36
33
|
|
|
37
|
-
type InferPropertyValue<
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
type InferPropertyValue<Value> =
|
|
35
|
+
Value extends Constructor<infer Instance>
|
|
36
|
+
? Instance
|
|
37
|
+
: Value extends Schematic<infer Model>
|
|
38
|
+
? Model
|
|
39
|
+
: Value extends ValueName
|
|
40
|
+
? Values[Value & ValueName]
|
|
41
|
+
: Value extends Schema
|
|
42
|
+
? Infer<Value>
|
|
43
|
+
: never;
|
|
40
44
|
|
|
41
45
|
type InferRequiredKeys<Model extends Schema> = keyof {
|
|
42
46
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
type
|
|
46
|
-
?
|
|
47
|
-
|
|
49
|
+
type InferSchemaEntry<Value> = Value extends (infer Item)[]
|
|
50
|
+
? InferSchemaEntryValue<Item>
|
|
51
|
+
: InferSchemaEntryValue<Value>;
|
|
52
|
+
|
|
53
|
+
type InferSchemaEntryValue<Value> =
|
|
54
|
+
Value extends Constructor<infer Instance>
|
|
55
|
+
? Instance
|
|
48
56
|
: Value extends Schematic<infer Model>
|
|
49
57
|
? Model
|
|
50
|
-
: Value extends
|
|
51
|
-
?
|
|
52
|
-
: Value extends
|
|
53
|
-
?
|
|
54
|
-
: Value
|
|
55
|
-
|
|
58
|
+
: Value extends SchemaProperty
|
|
59
|
+
? InferPropertyType<Value['$type']>
|
|
60
|
+
: Value extends ValueName
|
|
61
|
+
? Values[Value & ValueName]
|
|
62
|
+
: Value extends Schema
|
|
63
|
+
? Infer<Value>
|
|
64
|
+
: never;
|
|
56
65
|
|
|
57
66
|
type IsOptionalProperty<Value> = Value extends SchemaProperty
|
|
58
67
|
? Value['$required'] extends false
|
|
@@ -65,6 +74,14 @@ type LastOfUnion<Value> =
|
|
|
65
74
|
? Item
|
|
66
75
|
: never;
|
|
67
76
|
|
|
77
|
+
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
|
|
78
|
+
? [ToValueType<Head>, ...MapToValueTypes<Tail>]
|
|
79
|
+
: [];
|
|
80
|
+
|
|
81
|
+
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
|
|
82
|
+
? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>]
|
|
83
|
+
: [];
|
|
84
|
+
|
|
68
85
|
/**
|
|
69
86
|
* A nested schema with optional requirement flag
|
|
70
87
|
*/
|
|
@@ -83,10 +100,10 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
83
100
|
*/
|
|
84
101
|
export type Schema = SchemaIndex;
|
|
85
102
|
|
|
86
|
-
type SchemaEntry = NestedSchema | SchemaProperty | Schematic<unknown> | ValueName
|
|
103
|
+
type SchemaEntry = Constructor | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
|
|
87
104
|
|
|
88
105
|
interface SchemaIndex {
|
|
89
|
-
[key: string]: SchemaEntry;
|
|
106
|
+
[key: string]: SchemaEntry | SchemaEntry[];
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
/**
|
|
@@ -97,27 +114,67 @@ export type SchemaProperty = {
|
|
|
97
114
|
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
98
115
|
};
|
|
99
116
|
|
|
100
|
-
type SchemaPropertyType = Schema | Schematic<unknown> | ValueName;
|
|
117
|
+
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
101
118
|
|
|
102
|
-
type ToSchemaPropertyType<Value> = UnwrapSingle<
|
|
119
|
+
type ToSchemaPropertyType<Value> = UnwrapSingle<
|
|
120
|
+
DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
|
|
121
|
+
>;
|
|
103
122
|
|
|
104
123
|
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject
|
|
105
124
|
? TypedSchema<Value>
|
|
106
|
-
:
|
|
107
|
-
|
|
108
|
-
type ToSchemaType<Value> = UnwrapSingle<UnionToTuple<
|
|
109
|
-
|
|
110
|
-
type
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
: ToValueType<Value>;
|
|
126
|
+
|
|
127
|
+
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
128
|
+
|
|
129
|
+
type ToValueType<Value> = Value extends unknown[]
|
|
130
|
+
? 'array'
|
|
131
|
+
: Value extends bigint
|
|
132
|
+
? 'bigint'
|
|
133
|
+
: Value extends boolean
|
|
134
|
+
? 'boolean'
|
|
135
|
+
: Value extends Date
|
|
136
|
+
? 'date'
|
|
137
|
+
: Value extends Function
|
|
138
|
+
? 'function'
|
|
139
|
+
: Value extends null
|
|
140
|
+
? 'null'
|
|
141
|
+
: Value extends number
|
|
142
|
+
? 'number'
|
|
143
|
+
: Value extends object
|
|
144
|
+
? 'object' | ((value: unknown) => value is Value)
|
|
145
|
+
: Value extends string
|
|
146
|
+
? 'string'
|
|
147
|
+
: Value extends symbol
|
|
148
|
+
? 'symbol'
|
|
149
|
+
: Value extends undefined
|
|
150
|
+
? 'undefined'
|
|
151
|
+
: (value: unknown) => value is Value;
|
|
152
|
+
|
|
153
|
+
type TuplePermutations<
|
|
154
|
+
Tuple extends unknown[],
|
|
155
|
+
Elput extends unknown[] = [],
|
|
156
|
+
> = Tuple['length'] extends 0
|
|
157
|
+
? Elput
|
|
158
|
+
: {
|
|
159
|
+
[Key in keyof Tuple]: TuplePermutations<
|
|
160
|
+
TupleRemoveAt<Tuple, Key & `${number}`>,
|
|
161
|
+
[...Elput, Tuple[Key]]
|
|
162
|
+
>;
|
|
163
|
+
}[keyof Tuple & `${number}`];
|
|
164
|
+
|
|
165
|
+
type TupleRemoveAt<
|
|
166
|
+
Items extends unknown[],
|
|
167
|
+
Item extends string,
|
|
168
|
+
Prefix extends unknown[] = [],
|
|
169
|
+
> = Items extends [infer Head, ...infer Tail]
|
|
170
|
+
? `${Prefix['length']}` extends Item
|
|
171
|
+
? [...Prefix, ...Tail]
|
|
172
|
+
: TupleRemoveAt<Tail, Item, [...Prefix, Head]>
|
|
173
|
+
: Prefix;
|
|
117
174
|
|
|
118
175
|
export type TypedPropertyOptional<Value> = {
|
|
119
176
|
$required: false;
|
|
120
|
-
$type: ToSchemaPropertyType<Value
|
|
177
|
+
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
121
178
|
};
|
|
122
179
|
|
|
123
180
|
export type TypedPropertyRequired<Value> = {
|
|
@@ -145,11 +202,11 @@ type TypedSchemaOptional<Model extends PlainObject> = {
|
|
|
145
202
|
} & TypedSchema<Model>;
|
|
146
203
|
|
|
147
204
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
148
|
-
$required?:
|
|
205
|
+
$required?: true;
|
|
149
206
|
} & TypedSchema<Model>;
|
|
150
207
|
|
|
151
|
-
type UnionToIntersection<Value> = (Value extends unknown ? (
|
|
152
|
-
|
|
208
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (
|
|
209
|
+
value: infer Item,
|
|
153
210
|
) => void
|
|
154
211
|
? Item
|
|
155
212
|
: never;
|
|
@@ -158,7 +215,11 @@ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
|
|
|
158
215
|
? Items
|
|
159
216
|
: UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
160
217
|
|
|
161
|
-
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
218
|
+
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
219
|
+
? Only
|
|
220
|
+
: Value['length'] extends 1 | 2 | 3 | 4 | 5
|
|
221
|
+
? TuplePermutations<Value>
|
|
222
|
+
: Value;
|
|
162
223
|
|
|
163
224
|
export type ValidatedProperty = {
|
|
164
225
|
required: boolean;
|
|
@@ -168,6 +229,7 @@ export type ValidatedProperty = {
|
|
|
168
229
|
export type ValidatedPropertyType = Schematic<unknown> | ValueName;
|
|
169
230
|
|
|
170
231
|
export type ValidatedSchema = {
|
|
232
|
+
enabled: boolean;
|
|
171
233
|
keys: {
|
|
172
234
|
array: string[];
|
|
173
235
|
set: Set<string>;
|
|
@@ -180,8 +242,6 @@ export type ValidatedSchema = {
|
|
|
180
242
|
*/
|
|
181
243
|
export type ValueName = keyof Values;
|
|
182
244
|
|
|
183
|
-
type ValueNameFallbacks = 'date-like' | 'numerical';
|
|
184
|
-
|
|
185
245
|
/**
|
|
186
246
|
* Map of type names to their TypeScript/validatable equivalents
|
|
187
247
|
*/
|
|
@@ -190,11 +250,9 @@ export type Values = {
|
|
|
190
250
|
bigint: bigint;
|
|
191
251
|
boolean: boolean;
|
|
192
252
|
date: Date;
|
|
193
|
-
'date-like': number | string | Date;
|
|
194
253
|
function: Function;
|
|
195
254
|
null: null;
|
|
196
255
|
number: number;
|
|
197
|
-
numerical: bigint | number;
|
|
198
256
|
object: object;
|
|
199
257
|
string: string;
|
|
200
258
|
symbol: symbol;
|
package/src/schematic.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {SCHEMATIC_NAME} from './constants';
|
|
3
3
|
import type {Infer, Schema, TypedSchema, ValidatedSchema} from './models';
|
|
4
|
-
import {
|
|
4
|
+
import {getSchema} from './validation/schema.validation';
|
|
5
5
|
import {validateValue} from './validation/value.validation';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -11,10 +11,9 @@ export class Schematic<Model> {
|
|
|
11
11
|
declare private readonly $schematic: true;
|
|
12
12
|
|
|
13
13
|
#schema: ValidatedSchema;
|
|
14
|
-
#validatable: boolean;
|
|
15
14
|
|
|
16
|
-
get
|
|
17
|
-
return this.#
|
|
15
|
+
get enabled(): boolean {
|
|
16
|
+
return this.#schema.enabled;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
constructor(schema: Model) {
|
|
@@ -22,16 +21,16 @@ export class Schematic<Model> {
|
|
|
22
21
|
value: true,
|
|
23
22
|
});
|
|
24
23
|
|
|
25
|
-
this.#schema =
|
|
24
|
+
this.#schema = getSchema(schema);
|
|
26
25
|
|
|
27
|
-
this.#
|
|
26
|
+
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Does the value match the schema?
|
|
32
31
|
*/
|
|
33
32
|
is(value: unknown): value is Model {
|
|
34
|
-
return this.#
|
|
33
|
+
return this.#schema.enabled && validateValue(this.#schema, value);
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
1
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
3
|
import {smush} from '@oscarpalmer/atoms/value';
|
|
3
4
|
import {
|
|
5
|
+
EXPRESSION_HAS_NUMBER,
|
|
6
|
+
EXPRESSION_INDEX,
|
|
7
|
+
EXPRESSION_PROPERTY,
|
|
4
8
|
PROPERTY_REQUIRED,
|
|
5
9
|
PROPERTY_TYPE,
|
|
6
10
|
TYPE_ALL,
|
|
7
11
|
TYPE_OBJECT,
|
|
8
12
|
TYPE_UNDEFINED,
|
|
9
13
|
} from '../constants';
|
|
10
|
-
import {isSchematic} from '../is';
|
|
14
|
+
import {isInstance, isSchematic} from '../is';
|
|
11
15
|
import type {Schema, ValidatedPropertyType, ValidatedSchema} from '../models';
|
|
12
16
|
|
|
13
17
|
function addPropertyType(
|
|
@@ -39,6 +43,21 @@ function addPropertyType(
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
export function getSchema(schema: unknown): ValidatedSchema {
|
|
47
|
+
const validated: ValidatedSchema = {
|
|
48
|
+
enabled: false,
|
|
49
|
+
keys: {
|
|
50
|
+
array: [],
|
|
51
|
+
set: new Set<string>(),
|
|
52
|
+
},
|
|
53
|
+
properties: {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return typeof schema === 'object' && schema !== null
|
|
57
|
+
? getValidatedSchema(schema as Schema, validated)
|
|
58
|
+
: validated;
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
function getTypes(
|
|
43
62
|
value: unknown,
|
|
44
63
|
validated: ValidatedSchema,
|
|
@@ -51,14 +70,21 @@ function getTypes(
|
|
|
51
70
|
|
|
52
71
|
for (let index = 0; index < length; index += 1) {
|
|
53
72
|
const type = values[index];
|
|
73
|
+
const typeOfType = typeof type;
|
|
54
74
|
|
|
55
|
-
if (isSchematic(type) || (
|
|
75
|
+
if (isSchematic(type) || (typeOfType === 'string' && TYPE_ALL.has(type as never))) {
|
|
56
76
|
propertyTypes.push(type as never);
|
|
57
77
|
|
|
58
78
|
continue;
|
|
59
79
|
}
|
|
60
80
|
|
|
61
|
-
if (
|
|
81
|
+
if (typeOfType === 'function') {
|
|
82
|
+
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
83
|
+
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeOfType !== 'object' || type === null) {
|
|
62
88
|
continue;
|
|
63
89
|
}
|
|
64
90
|
|
|
@@ -100,11 +126,11 @@ function getValidatedSchema(
|
|
|
100
126
|
arrayKeys.add(key);
|
|
101
127
|
}
|
|
102
128
|
|
|
103
|
-
if (
|
|
129
|
+
if (EXPRESSION_PROPERTY.test(key)) {
|
|
104
130
|
continue;
|
|
105
131
|
}
|
|
106
132
|
|
|
107
|
-
if (
|
|
133
|
+
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ''))) {
|
|
108
134
|
continue;
|
|
109
135
|
}
|
|
110
136
|
|
|
@@ -129,17 +155,3 @@ function getValidatedSchema(
|
|
|
129
155
|
|
|
130
156
|
return validated;
|
|
131
157
|
}
|
|
132
|
-
|
|
133
|
-
export function validateSchema(schema: unknown): ValidatedSchema {
|
|
134
|
-
const validated: ValidatedSchema = {
|
|
135
|
-
keys: {
|
|
136
|
-
array: [],
|
|
137
|
-
set: new Set<string>(),
|
|
138
|
-
},
|
|
139
|
-
properties: {},
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
return typeof schema === 'object' && schema !== null
|
|
143
|
-
? getValidatedSchema(schema as Schema, validated)
|
|
144
|
-
: validated;
|
|
145
|
-
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {smush} from '@oscarpalmer/atoms/value';
|
|
3
|
-
import {
|
|
4
|
-
import {isDateLike} from '../is';
|
|
3
|
+
import {TYPE_UNDEFINED} from '../constants';
|
|
5
4
|
import type {ValidatedPropertyType, ValidatedSchema, Values} from '../models';
|
|
6
5
|
|
|
7
6
|
export function validateType(type: ValidatedPropertyType, value: unknown): boolean {
|
|
8
|
-
|
|
7
|
+
switch (true) {
|
|
8
|
+
case typeof type === 'function':
|
|
9
|
+
return (type as any)(value);
|
|
10
|
+
|
|
11
|
+
case typeof type === 'string':
|
|
12
|
+
return validators[type](value);
|
|
13
|
+
|
|
14
|
+
default:
|
|
15
|
+
return type.is(value);
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export function validateValue(validated: ValidatedSchema, obj: unknown): boolean {
|
|
@@ -50,7 +58,7 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
|
|
|
50
58
|
const type = property.types[typeIndex];
|
|
51
59
|
|
|
52
60
|
if (validateType(type, value)) {
|
|
53
|
-
if (type !==
|
|
61
|
+
if ((type as never) !== 'object') {
|
|
54
62
|
ignore.add(key);
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -75,11 +83,9 @@ const validators: Record<keyof Values, (value: unknown) => boolean> = {
|
|
|
75
83
|
bigint: value => typeof value === 'bigint',
|
|
76
84
|
boolean: value => typeof value === 'boolean',
|
|
77
85
|
date: value => value instanceof Date,
|
|
78
|
-
'date-like': isDateLike,
|
|
79
86
|
function: value => typeof value === 'function',
|
|
80
87
|
null: value => value === null,
|
|
81
88
|
number: value => typeof value === 'number' && !Number.isNaN(value),
|
|
82
|
-
numerical: value => validators.bigint(value) || validators.number(value),
|
|
83
89
|
object: value => typeof value === 'object' && value !== null,
|
|
84
90
|
string: value => typeof value === 'string',
|
|
85
91
|
symbol: value => typeof value === 'symbol',
|
package/types/constants.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Values } from './models';
|
|
2
|
+
export declare const EXPRESSION_HAS_NUMBER: RegExp;
|
|
3
|
+
export declare const EXPRESSION_INDEX: RegExp;
|
|
4
|
+
export declare const EXPRESSION_PROPERTY: RegExp;
|
|
2
5
|
export declare const PROPERTY_REQUIRED = "$required";
|
|
3
6
|
export declare const PROPERTY_TYPE = "$type";
|
|
4
7
|
export declare const SCHEMATIC_NAME = "$schematic";
|
package/types/index.d.ts
CHANGED
package/types/is.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Constructor } from './models';
|
|
1
2
|
import type { Schematic } from './schematic';
|
|
2
|
-
export declare function
|
|
3
|
+
export declare function isInstance<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
|
|
3
4
|
export declare function isSchematic(value: unknown): value is Schematic<never>;
|
package/types/models.d.ts
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import type { PlainObject, Simplify } from '@oscarpalmer/atoms/models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
|
+
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
4
|
+
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
5
|
+
infer Head,
|
|
6
|
+
...infer Tail
|
|
7
|
+
] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
|
|
3
8
|
/**
|
|
4
9
|
* Infer the TypeScript type from a schema definition
|
|
5
10
|
*/
|
|
6
11
|
export type Infer<Model extends Schema> = Simplify<{
|
|
7
|
-
[Key in InferRequiredKeys<Model>]:
|
|
12
|
+
[Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
|
|
8
13
|
} & {
|
|
9
|
-
[Key in InferOptionalKeys<Model>]?:
|
|
14
|
+
[Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
|
|
10
15
|
}>;
|
|
11
|
-
type InferEntryValue<Value> = Value extends ValueName ? Values[Value] : Value extends ValueName[] ? Values[Value[number]] : never;
|
|
12
16
|
type InferOptionalKeys<Model extends Schema> = keyof {
|
|
13
17
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
|
|
14
18
|
};
|
|
15
|
-
type InferPropertyType<Value> = Value extends (infer Item)[] ?
|
|
16
|
-
type InferPropertyValue<
|
|
19
|
+
type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValue<Item> : InferPropertyValue<Value>;
|
|
20
|
+
type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
17
21
|
type InferRequiredKeys<Model extends Schema> = keyof {
|
|
18
22
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
19
23
|
};
|
|
20
|
-
type
|
|
24
|
+
type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
|
|
25
|
+
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
21
26
|
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
|
|
22
27
|
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
|
|
28
|
+
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
|
|
29
|
+
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
23
30
|
/**
|
|
24
31
|
* A nested schema with optional requirement flag
|
|
25
32
|
*/
|
|
@@ -34,9 +41,9 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
34
41
|
* A schema for validating objects
|
|
35
42
|
*/
|
|
36
43
|
export type Schema = SchemaIndex;
|
|
37
|
-
type SchemaEntry = NestedSchema | SchemaProperty | Schematic<unknown> | ValueName
|
|
44
|
+
type SchemaEntry = Constructor | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
|
|
38
45
|
interface SchemaIndex {
|
|
39
|
-
[key: string]: SchemaEntry;
|
|
46
|
+
[key: string]: SchemaEntry | SchemaEntry[];
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* A property definition with explicit type(s) and optional requirement flag
|
|
@@ -45,18 +52,21 @@ export type SchemaProperty = {
|
|
|
45
52
|
$required?: boolean;
|
|
46
53
|
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
47
54
|
};
|
|
48
|
-
type SchemaPropertyType = Schema | Schematic<unknown> | ValueName;
|
|
49
|
-
type ToSchemaPropertyType<Value> = UnwrapSingle<UnionToTuple<
|
|
50
|
-
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> :
|
|
51
|
-
type ToSchemaType<Value> = UnwrapSingle<UnionToTuple<
|
|
52
|
-
type
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
56
|
+
type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
|
|
57
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
58
|
+
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
59
|
+
type ToValueType<Value> = Value extends unknown[] ? 'array' : Value extends bigint ? 'bigint' : Value extends boolean ? 'boolean' : Value extends Date ? 'date' : Value extends Function ? 'function' : Value extends null ? 'null' : Value extends number ? 'number' : Value extends object ? 'object' | ((value: unknown) => value is Value) : Value extends string ? 'string' : Value extends symbol ? 'symbol' : Value extends undefined ? 'undefined' : (value: unknown) => value is Value;
|
|
60
|
+
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : {
|
|
61
|
+
[Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [
|
|
62
|
+
...Elput,
|
|
63
|
+
Tuple[Key]
|
|
64
|
+
]>;
|
|
65
|
+
}[keyof Tuple & `${number}`];
|
|
66
|
+
type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends unknown[] = []> = Items extends [infer Head, ...infer Tail] ? `${Prefix['length']}` extends Item ? [...Prefix, ...Tail] : TupleRemoveAt<Tail, Item, [...Prefix, Head]> : Prefix;
|
|
57
67
|
export type TypedPropertyOptional<Value> = {
|
|
58
68
|
$required: false;
|
|
59
|
-
$type: ToSchemaPropertyType<Value
|
|
69
|
+
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
60
70
|
};
|
|
61
71
|
export type TypedPropertyRequired<Value> = {
|
|
62
72
|
$required?: true;
|
|
@@ -74,17 +84,18 @@ type TypedSchemaOptional<Model extends PlainObject> = {
|
|
|
74
84
|
$required: false;
|
|
75
85
|
} & TypedSchema<Model>;
|
|
76
86
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
77
|
-
$required?:
|
|
87
|
+
$required?: true;
|
|
78
88
|
} & TypedSchema<Model>;
|
|
79
|
-
type UnionToIntersection<Value> = (Value extends unknown ? (
|
|
89
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
|
|
80
90
|
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
81
|
-
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value;
|
|
91
|
+
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
|
|
82
92
|
export type ValidatedProperty = {
|
|
83
93
|
required: boolean;
|
|
84
94
|
types: ValidatedPropertyType[];
|
|
85
95
|
};
|
|
86
96
|
export type ValidatedPropertyType = Schematic<unknown> | ValueName;
|
|
87
97
|
export type ValidatedSchema = {
|
|
98
|
+
enabled: boolean;
|
|
88
99
|
keys: {
|
|
89
100
|
array: string[];
|
|
90
101
|
set: Set<string>;
|
|
@@ -95,7 +106,6 @@ export type ValidatedSchema = {
|
|
|
95
106
|
* Valid type name strings
|
|
96
107
|
*/
|
|
97
108
|
export type ValueName = keyof Values;
|
|
98
|
-
type ValueNameFallbacks = 'date-like' | 'numerical';
|
|
99
109
|
/**
|
|
100
110
|
* Map of type names to their TypeScript/validatable equivalents
|
|
101
111
|
*/
|
|
@@ -104,11 +114,9 @@ export type Values = {
|
|
|
104
114
|
bigint: bigint;
|
|
105
115
|
boolean: boolean;
|
|
106
116
|
date: Date;
|
|
107
|
-
'date-like': number | string | Date;
|
|
108
117
|
function: Function;
|
|
109
118
|
null: null;
|
|
110
119
|
number: number;
|
|
111
|
-
numerical: bigint | number;
|
|
112
120
|
object: object;
|
|
113
121
|
string: string;
|
|
114
122
|
symbol: symbol;
|
package/types/schematic.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { Infer, Schema, TypedSchema } from './models';
|
|
|
6
6
|
export declare class Schematic<Model> {
|
|
7
7
|
#private;
|
|
8
8
|
private readonly $schematic;
|
|
9
|
-
get
|
|
9
|
+
get enabled(): boolean;
|
|
10
10
|
constructor(schema: Model);
|
|
11
11
|
/**
|
|
12
12
|
* Does the value match the schema?
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ValidatedSchema } from '../models';
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function getSchema(schema: unknown): ValidatedSchema;
|