@oscarpalmer/jhunal 0.1.0 → 0.2.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/index.cjs +109 -0
- package/dist/index.js +106 -0
- package/package.json +5 -1
- package/src/index.ts +282 -0
- package/types/index.d.cts +346 -0
- package/types/index.d.ts +74 -0
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,111 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
|
+
|
|
5
|
+
const types = /* @__PURE__ */ new Set([
|
|
6
|
+
"array",
|
|
7
|
+
"bigint",
|
|
8
|
+
"boolean",
|
|
9
|
+
"date",
|
|
10
|
+
"function",
|
|
11
|
+
"null",
|
|
12
|
+
"number",
|
|
13
|
+
"object",
|
|
14
|
+
"string",
|
|
15
|
+
"symbol",
|
|
16
|
+
"undefined"
|
|
17
|
+
]);
|
|
18
|
+
const validators = {
|
|
19
|
+
array: Array.isArray,
|
|
20
|
+
bigint: (value) => typeof value === "bigint",
|
|
21
|
+
boolean: (value) => typeof value === "boolean",
|
|
22
|
+
date: (value) => value instanceof Date,
|
|
23
|
+
function: (value) => typeof value === "function",
|
|
24
|
+
null: (value) => value === null,
|
|
25
|
+
number: (value) => typeof value === "number",
|
|
26
|
+
object: (value) => typeof value === "object" && value !== null,
|
|
27
|
+
string: (value) => typeof value === "string",
|
|
28
|
+
symbol: (value) => typeof value === "symbol",
|
|
29
|
+
undefined: (value) => value === void 0
|
|
30
|
+
};
|
|
31
|
+
function getTypes(value) {
|
|
32
|
+
return (Array.isArray(value) ? value : [value]).filter(
|
|
33
|
+
(item) => types.has(item)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function getValidatedSchema(schema) {
|
|
37
|
+
const validated = {
|
|
38
|
+
keys: [],
|
|
39
|
+
length: 0,
|
|
40
|
+
properties: {}
|
|
41
|
+
};
|
|
42
|
+
if (typeof schema !== "object" || schema === null) {
|
|
43
|
+
return validated;
|
|
44
|
+
}
|
|
45
|
+
const keys = Object.keys(schema);
|
|
46
|
+
const { length } = keys;
|
|
47
|
+
for (let index = 0; index < length; index += 1) {
|
|
48
|
+
const key = keys[index];
|
|
49
|
+
const value = schema[key];
|
|
50
|
+
let required = true;
|
|
51
|
+
let valueTypes;
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
valueTypes = getTypes(value);
|
|
54
|
+
} else if (typeof value === "object" && value !== null) {
|
|
55
|
+
if (typeof value.required === "boolean") {
|
|
56
|
+
required = value.required;
|
|
57
|
+
}
|
|
58
|
+
valueTypes = getTypes(value.type);
|
|
59
|
+
} else {
|
|
60
|
+
valueTypes = getTypes(value);
|
|
61
|
+
}
|
|
62
|
+
if (valueTypes.length > 0) {
|
|
63
|
+
if (!required && !valueTypes.includes("undefined")) {
|
|
64
|
+
valueTypes.push("undefined");
|
|
65
|
+
}
|
|
66
|
+
validated.keys.push(key);
|
|
67
|
+
validated.properties[key] = {
|
|
68
|
+
required,
|
|
69
|
+
types: valueTypes
|
|
70
|
+
};
|
|
71
|
+
validated.length += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return validated;
|
|
75
|
+
}
|
|
76
|
+
function schematic(schema) {
|
|
77
|
+
const validated = getValidatedSchema(schema);
|
|
78
|
+
const canValidate = validated.length > 0;
|
|
79
|
+
return Object.freeze({
|
|
80
|
+
is: (value) => canValidate && validate(validated, value)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function validate(validated, obj) {
|
|
84
|
+
if (typeof obj !== "object" || obj === null) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
outer: for (let index = 0; index < validated.length; index += 1) {
|
|
88
|
+
const key = validated.keys[index];
|
|
89
|
+
const property = validated.properties[key];
|
|
90
|
+
const value = obj[key];
|
|
91
|
+
if (value === void 0 && property.required && !property.types.includes("undefined")) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const typesLength = property.types.length;
|
|
95
|
+
if (typesLength === 1) {
|
|
96
|
+
if (!validators[property.types[0]](value)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
102
|
+
if (validators[property.types[typeIndex]](value)) {
|
|
103
|
+
continue outer;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
exports.schematic = schematic;
|
package/dist/index.js
CHANGED
|
@@ -1 +1,107 @@
|
|
|
1
|
+
const types = /* @__PURE__ */ new Set([
|
|
2
|
+
"array",
|
|
3
|
+
"bigint",
|
|
4
|
+
"boolean",
|
|
5
|
+
"date",
|
|
6
|
+
"function",
|
|
7
|
+
"null",
|
|
8
|
+
"number",
|
|
9
|
+
"object",
|
|
10
|
+
"string",
|
|
11
|
+
"symbol",
|
|
12
|
+
"undefined"
|
|
13
|
+
]);
|
|
14
|
+
const validators = {
|
|
15
|
+
array: Array.isArray,
|
|
16
|
+
bigint: (value) => typeof value === "bigint",
|
|
17
|
+
boolean: (value) => typeof value === "boolean",
|
|
18
|
+
date: (value) => value instanceof Date,
|
|
19
|
+
function: (value) => typeof value === "function",
|
|
20
|
+
null: (value) => value === null,
|
|
21
|
+
number: (value) => typeof value === "number",
|
|
22
|
+
object: (value) => typeof value === "object" && value !== null,
|
|
23
|
+
string: (value) => typeof value === "string",
|
|
24
|
+
symbol: (value) => typeof value === "symbol",
|
|
25
|
+
undefined: (value) => value === void 0
|
|
26
|
+
};
|
|
27
|
+
function getTypes(value) {
|
|
28
|
+
return (Array.isArray(value) ? value : [value]).filter(
|
|
29
|
+
(item) => types.has(item)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function getValidatedSchema(schema) {
|
|
33
|
+
const validated = {
|
|
34
|
+
keys: [],
|
|
35
|
+
length: 0,
|
|
36
|
+
properties: {}
|
|
37
|
+
};
|
|
38
|
+
if (typeof schema !== "object" || schema === null) {
|
|
39
|
+
return validated;
|
|
40
|
+
}
|
|
41
|
+
const keys = Object.keys(schema);
|
|
42
|
+
const { length } = keys;
|
|
43
|
+
for (let index = 0; index < length; index += 1) {
|
|
44
|
+
const key = keys[index];
|
|
45
|
+
const value = schema[key];
|
|
46
|
+
let required = true;
|
|
47
|
+
let valueTypes;
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
valueTypes = getTypes(value);
|
|
50
|
+
} else if (typeof value === "object" && value !== null) {
|
|
51
|
+
if (typeof value.required === "boolean") {
|
|
52
|
+
required = value.required;
|
|
53
|
+
}
|
|
54
|
+
valueTypes = getTypes(value.type);
|
|
55
|
+
} else {
|
|
56
|
+
valueTypes = getTypes(value);
|
|
57
|
+
}
|
|
58
|
+
if (valueTypes.length > 0) {
|
|
59
|
+
if (!required && !valueTypes.includes("undefined")) {
|
|
60
|
+
valueTypes.push("undefined");
|
|
61
|
+
}
|
|
62
|
+
validated.keys.push(key);
|
|
63
|
+
validated.properties[key] = {
|
|
64
|
+
required,
|
|
65
|
+
types: valueTypes
|
|
66
|
+
};
|
|
67
|
+
validated.length += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return validated;
|
|
71
|
+
}
|
|
72
|
+
function schematic(schema) {
|
|
73
|
+
const validated = getValidatedSchema(schema);
|
|
74
|
+
const canValidate = validated.length > 0;
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
is: (value) => canValidate && validate(validated, value)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function validate(validated, obj) {
|
|
80
|
+
if (typeof obj !== "object" || obj === null) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
outer: for (let index = 0; index < validated.length; index += 1) {
|
|
84
|
+
const key = validated.keys[index];
|
|
85
|
+
const property = validated.properties[key];
|
|
86
|
+
const value = obj[key];
|
|
87
|
+
if (value === void 0 && property.required && !property.types.includes("undefined")) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const typesLength = property.types.length;
|
|
91
|
+
if (typesLength === 1) {
|
|
92
|
+
if (!validators[property.types[0]](value)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
98
|
+
if (validators[property.types[typeIndex]](value)) {
|
|
99
|
+
continue outer;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
1
106
|
|
|
107
|
+
export { schematic };
|
package/package.json
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
"name": "Oscar Palmér",
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@oscarpalmer/atoms": "^0.94",
|
|
8
|
+
"type-fest": "^4.39"
|
|
9
|
+
},
|
|
6
10
|
"description": "Flies free beneath the glistening moons…",
|
|
7
11
|
"devDependencies": {
|
|
8
12
|
"@biomejs/biome": "^1.9",
|
|
@@ -46,5 +50,5 @@
|
|
|
46
50
|
},
|
|
47
51
|
"type": "module",
|
|
48
52
|
"types": "./types/index.d.cts",
|
|
49
|
-
"version": "0.
|
|
53
|
+
"version": "0.2.0"
|
|
50
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
OptionalKeysOf,
|
|
3
|
+
RequiredKeysOf,
|
|
4
|
+
Simplify,
|
|
5
|
+
UnionToTuple,
|
|
6
|
+
} from 'type-fest';
|
|
7
|
+
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
8
|
+
|
|
9
|
+
type GetKey<Value> = {
|
|
10
|
+
[Key in keyof Values]: Value extends Values[Key] ? Key : never;
|
|
11
|
+
}[keyof Values];
|
|
12
|
+
|
|
13
|
+
type GetTypes<Value extends unknown[]> = Value extends [infer Single]
|
|
14
|
+
? Single
|
|
15
|
+
: Value;
|
|
16
|
+
|
|
17
|
+
type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
|
|
18
|
+
|
|
19
|
+
type InferProperty<Value> = Value extends Property
|
|
20
|
+
? Value['type'] extends keyof Values
|
|
21
|
+
? Values[Value['type']]
|
|
22
|
+
: Value['type'] extends (keyof Values)[]
|
|
23
|
+
? Values[Value['type'][number]]
|
|
24
|
+
: never
|
|
25
|
+
: never;
|
|
26
|
+
|
|
27
|
+
type InferValue<Value> = Value extends keyof Values
|
|
28
|
+
? Values[Value]
|
|
29
|
+
: Value extends (keyof Values)[]
|
|
30
|
+
? Values[Value[number]]
|
|
31
|
+
: never;
|
|
32
|
+
|
|
33
|
+
type Inferred<Model extends Schema> = {
|
|
34
|
+
[Key in InferredRequiredProperties<Model>]: Model[Key] extends Property
|
|
35
|
+
? InferProperty<Model[Key]>
|
|
36
|
+
: InferValue<Model[Key]>;
|
|
37
|
+
} & {
|
|
38
|
+
[Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property
|
|
39
|
+
? InferProperty<Model[Key]>
|
|
40
|
+
: never;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type InferredOptionalProperties<Model extends Schema> = {
|
|
44
|
+
[Key in keyof Model]: Model[Key] extends Property
|
|
45
|
+
? Model[Key]['required'] extends false
|
|
46
|
+
? Key
|
|
47
|
+
: never
|
|
48
|
+
: never;
|
|
49
|
+
}[keyof Model];
|
|
50
|
+
|
|
51
|
+
type InferredRequiredProperties<Model extends Schema> = {
|
|
52
|
+
[Key in keyof Model]: Model[Key] extends Property
|
|
53
|
+
? Model[Key]['required'] extends false
|
|
54
|
+
? never
|
|
55
|
+
: Key
|
|
56
|
+
: Key;
|
|
57
|
+
}[keyof Model];
|
|
58
|
+
|
|
59
|
+
type OptionalProperty<Type> = {
|
|
60
|
+
required: false;
|
|
61
|
+
type: GetType<Type>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type Property = {
|
|
65
|
+
required?: boolean;
|
|
66
|
+
type: keyof Values | (keyof Values)[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type RequiredProperty<Type> = {
|
|
70
|
+
required: true;
|
|
71
|
+
type: GetType<Type>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A schema for validating objects
|
|
76
|
+
*/
|
|
77
|
+
export type Schema = Record<string, keyof Values | (keyof Values)[] | Property>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A schematic for validating objects
|
|
81
|
+
*/
|
|
82
|
+
export type Schematic<Model> = {
|
|
83
|
+
/**
|
|
84
|
+
* Does the value match the schema?
|
|
85
|
+
*/
|
|
86
|
+
is(value: unknown): value is Model;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type Typed = Record<string, unknown>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A typed schema for validating objects
|
|
93
|
+
*/
|
|
94
|
+
export type TypedSchema<Model extends Typed> = Simplify<
|
|
95
|
+
{
|
|
96
|
+
[Key in RequiredKeysOf<Model>]:
|
|
97
|
+
| GetType<Model[Key]>
|
|
98
|
+
| RequiredProperty<Model[Key]>;
|
|
99
|
+
} & {
|
|
100
|
+
[Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
|
|
101
|
+
}
|
|
102
|
+
>;
|
|
103
|
+
|
|
104
|
+
type ValidatedProperty = {
|
|
105
|
+
required: boolean;
|
|
106
|
+
types: (keyof Values)[];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type ValidatedSchema = {
|
|
110
|
+
keys: string[];
|
|
111
|
+
length: number;
|
|
112
|
+
properties: Record<string, ValidatedProperty>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type Values = {
|
|
116
|
+
array: unknown[];
|
|
117
|
+
bigint: bigint;
|
|
118
|
+
boolean: boolean;
|
|
119
|
+
date: Date;
|
|
120
|
+
// biome-ignore lint/complexity/noBannedTypes: it's the most basic value type, so I think it's ok
|
|
121
|
+
function: Function;
|
|
122
|
+
null: null;
|
|
123
|
+
number: number;
|
|
124
|
+
object: object;
|
|
125
|
+
string: string;
|
|
126
|
+
symbol: symbol;
|
|
127
|
+
undefined: undefined;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//
|
|
131
|
+
|
|
132
|
+
const types = new Set<keyof Values>([
|
|
133
|
+
'array',
|
|
134
|
+
'bigint',
|
|
135
|
+
'boolean',
|
|
136
|
+
'date',
|
|
137
|
+
'function',
|
|
138
|
+
'null',
|
|
139
|
+
'number',
|
|
140
|
+
'object',
|
|
141
|
+
'string',
|
|
142
|
+
'symbol',
|
|
143
|
+
'undefined',
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const validators: Record<keyof Values, (value: unknown) => boolean> = {
|
|
147
|
+
array: Array.isArray,
|
|
148
|
+
bigint: value => typeof value === 'bigint',
|
|
149
|
+
boolean: value => typeof value === 'boolean',
|
|
150
|
+
date: value => value instanceof Date,
|
|
151
|
+
function: value => typeof value === 'function',
|
|
152
|
+
null: value => value === null,
|
|
153
|
+
number: value => typeof value === 'number',
|
|
154
|
+
object: value => typeof value === 'object' && value !== null,
|
|
155
|
+
string: value => typeof value === 'string',
|
|
156
|
+
symbol: value => typeof value === 'symbol',
|
|
157
|
+
undefined: value => value === undefined,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//
|
|
161
|
+
|
|
162
|
+
function getTypes(value: unknown): (keyof Values)[] {
|
|
163
|
+
return (Array.isArray(value) ? value : [value]).filter(item =>
|
|
164
|
+
types.has(item),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getValidatedSchema(schema: unknown): ValidatedSchema {
|
|
169
|
+
const validated: ValidatedSchema = {
|
|
170
|
+
keys: [],
|
|
171
|
+
length: 0,
|
|
172
|
+
properties: {},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (typeof schema !== 'object' || schema === null) {
|
|
176
|
+
return validated;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const keys = Object.keys(schema);
|
|
180
|
+
const {length} = keys;
|
|
181
|
+
|
|
182
|
+
for (let index = 0; index < length; index += 1) {
|
|
183
|
+
const key = keys[index];
|
|
184
|
+
const value = (schema as Schema)[key];
|
|
185
|
+
|
|
186
|
+
let required = true;
|
|
187
|
+
let valueTypes: (keyof Values)[];
|
|
188
|
+
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
valueTypes = getTypes(value);
|
|
191
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
192
|
+
if (typeof (value as PlainObject).required === 'boolean') {
|
|
193
|
+
required = (value as PlainObject).required as boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
valueTypes = getTypes((value as PlainObject).type);
|
|
197
|
+
} else {
|
|
198
|
+
valueTypes = getTypes(value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (valueTypes.length > 0) {
|
|
202
|
+
if (!required && !valueTypes.includes('undefined')) {
|
|
203
|
+
valueTypes.push('undefined');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
validated.keys.push(key);
|
|
207
|
+
|
|
208
|
+
validated.properties[key] = {
|
|
209
|
+
required,
|
|
210
|
+
types: valueTypes,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
validated.length += 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return validated;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create a schematic from a typed schema
|
|
222
|
+
*/
|
|
223
|
+
export function schematic<Model extends Typed>(
|
|
224
|
+
schema: TypedSchema<Model>,
|
|
225
|
+
): Schematic<Model>;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create a schematic from a schema
|
|
229
|
+
*/
|
|
230
|
+
export function schematic<Model extends Schema>(
|
|
231
|
+
schema: Model,
|
|
232
|
+
): Schematic<Inferred<Model>>;
|
|
233
|
+
|
|
234
|
+
export function schematic<Model extends Schema>(schema: Model) {
|
|
235
|
+
const validated = getValidatedSchema(schema);
|
|
236
|
+
|
|
237
|
+
const canValidate = validated.length > 0;
|
|
238
|
+
|
|
239
|
+
return Object.freeze({
|
|
240
|
+
is: (value: unknown) => canValidate && validate(validated, value),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function validate(validated: ValidatedSchema, obj: unknown): boolean {
|
|
245
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
outer: for (let index = 0; index < validated.length; index += 1) {
|
|
250
|
+
const key = validated.keys[index];
|
|
251
|
+
const property = validated.properties[key];
|
|
252
|
+
const value = (obj as PlainObject)[key];
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
value === undefined &&
|
|
256
|
+
property.required &&
|
|
257
|
+
!property.types.includes('undefined')
|
|
258
|
+
) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const typesLength = property.types.length;
|
|
263
|
+
|
|
264
|
+
if (typesLength === 1) {
|
|
265
|
+
if (!validators[property.types[0]](value)) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
273
|
+
if (validators[property.types[typeIndex]](value)) {
|
|
274
|
+
continue outer;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return true;
|
|
282
|
+
}
|
package/types/index.d.cts
CHANGED
|
@@ -1,3 +1,349 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
Convert a union type to an intersection type using [distributive conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
|
|
5
|
+
|
|
6
|
+
Inspired by [this Stack Overflow answer](https://stackoverflow.com/a/50375286/2172153).
|
|
7
|
+
|
|
8
|
+
@example
|
|
9
|
+
```
|
|
10
|
+
import type {UnionToIntersection} from 'type-fest';
|
|
11
|
+
|
|
12
|
+
type Union = {the(): void} | {great(arg: string): void} | {escape: boolean};
|
|
13
|
+
|
|
14
|
+
type Intersection = UnionToIntersection<Union>;
|
|
15
|
+
//=> {the(): void; great(arg: string): void; escape: boolean};
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A more applicable example which could make its way into your library code follows.
|
|
19
|
+
|
|
20
|
+
@example
|
|
21
|
+
```
|
|
22
|
+
import type {UnionToIntersection} from 'type-fest';
|
|
23
|
+
|
|
24
|
+
class CommandOne {
|
|
25
|
+
commands: {
|
|
26
|
+
a1: () => undefined,
|
|
27
|
+
b1: () => undefined,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class CommandTwo {
|
|
32
|
+
commands: {
|
|
33
|
+
a2: (argA: string) => undefined,
|
|
34
|
+
b2: (argB: string) => undefined,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const union = [new CommandOne(), new CommandTwo()].map(instance => instance.commands);
|
|
39
|
+
type Union = typeof union;
|
|
40
|
+
//=> {a1(): void; b1(): void} | {a2(argA: string): void; b2(argB: string): void}
|
|
41
|
+
|
|
42
|
+
type Intersection = UnionToIntersection<Union>;
|
|
43
|
+
//=> {a1(): void; b1(): void; a2(argA: string): void; b2(argB: string): void}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
@category Type
|
|
47
|
+
*/
|
|
48
|
+
export type UnionToIntersection<Union> = (
|
|
49
|
+
// `extends unknown` is always going to be the case and is used to convert the
|
|
50
|
+
// `Union` into a [distributive conditional
|
|
51
|
+
// type](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
|
|
52
|
+
Union extends unknown ? (distributedUnion: Union) => void : never) extends ((mergedIntersection: infer Intersection) => void) ? Intersection & Union : never;
|
|
53
|
+
/**
|
|
54
|
+
Extract all optional keys from the given type.
|
|
55
|
+
|
|
56
|
+
This is useful when you want to create a new type that contains different type values for the optional keys only.
|
|
57
|
+
|
|
58
|
+
@example
|
|
59
|
+
```
|
|
60
|
+
import type {OptionalKeysOf, Except} from 'type-fest';
|
|
61
|
+
|
|
62
|
+
interface User {
|
|
63
|
+
name: string;
|
|
64
|
+
surname: string;
|
|
65
|
+
|
|
66
|
+
luckyNumber?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const REMOVE_FIELD = Symbol('remove field symbol');
|
|
70
|
+
type UpdateOperation<Entity extends object> = Except<Partial<Entity>, OptionalKeysOf<Entity>> & {
|
|
71
|
+
[Key in OptionalKeysOf<Entity>]?: Entity[Key] | typeof REMOVE_FIELD;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const update1: UpdateOperation<User> = {
|
|
75
|
+
name: 'Alice'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const update2: UpdateOperation<User> = {
|
|
79
|
+
name: 'Bob',
|
|
80
|
+
luckyNumber: REMOVE_FIELD
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
@category Utilities
|
|
85
|
+
*/
|
|
86
|
+
export type OptionalKeysOf<BaseType extends object> = BaseType extends unknown // For distributing `BaseType`
|
|
87
|
+
? (keyof {
|
|
88
|
+
[Key in keyof BaseType as BaseType extends Record<Key, BaseType[Key]> ? never : Key]: never;
|
|
89
|
+
}) & (keyof BaseType) // Intersect with `keyof BaseType` to ensure result of `OptionalKeysOf<BaseType>` is always assignable to `keyof BaseType`
|
|
90
|
+
: never; // Should never happen
|
|
91
|
+
/**
|
|
92
|
+
Extract all required keys from the given type.
|
|
93
|
+
|
|
94
|
+
This is useful when you want to create a new type that contains different type values for the required keys only or use the list of keys for validation purposes, etc...
|
|
95
|
+
|
|
96
|
+
@example
|
|
97
|
+
```
|
|
98
|
+
import type {RequiredKeysOf} from 'type-fest';
|
|
99
|
+
|
|
100
|
+
declare function createValidation<Entity extends object, Key extends RequiredKeysOf<Entity> = RequiredKeysOf<Entity>>(field: Key, validator: (value: Entity[Key]) => boolean): ValidatorFn;
|
|
101
|
+
|
|
102
|
+
interface User {
|
|
103
|
+
name: string;
|
|
104
|
+
surname: string;
|
|
105
|
+
|
|
106
|
+
luckyNumber?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const validator1 = createValidation<User>('name', value => value.length < 25);
|
|
110
|
+
const validator2 = createValidation<User>('surname', value => value.length < 25);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
@category Utilities
|
|
114
|
+
*/
|
|
115
|
+
export type RequiredKeysOf<BaseType extends object> = BaseType extends unknown // For distributing `BaseType`
|
|
116
|
+
? Exclude<keyof BaseType, OptionalKeysOf<BaseType>> : never; // Should never happen
|
|
117
|
+
/**
|
|
118
|
+
Returns a boolean for whether the given type is `never`.
|
|
119
|
+
|
|
120
|
+
@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
|
|
121
|
+
@link https://stackoverflow.com/a/53984913/10292952
|
|
122
|
+
@link https://www.zhenghao.io/posts/ts-never
|
|
123
|
+
|
|
124
|
+
Useful in type utilities, such as checking if something does not occur.
|
|
125
|
+
|
|
126
|
+
@example
|
|
127
|
+
```
|
|
128
|
+
import type {IsNever, And} from 'type-fest';
|
|
129
|
+
|
|
130
|
+
// https://github.com/andnp/SimplyTyped/blob/master/src/types/strings.ts
|
|
131
|
+
type AreStringsEqual<A extends string, B extends string> =
|
|
132
|
+
And<
|
|
133
|
+
IsNever<Exclude<A, B>> extends true ? true : false,
|
|
134
|
+
IsNever<Exclude<B, A>> extends true ? true : false
|
|
135
|
+
>;
|
|
136
|
+
|
|
137
|
+
type EndIfEqual<I extends string, O extends string> =
|
|
138
|
+
AreStringsEqual<I, O> extends true
|
|
139
|
+
? never
|
|
140
|
+
: void;
|
|
141
|
+
|
|
142
|
+
function endIfEqual<I extends string, O extends string>(input: I, output: O): EndIfEqual<I, O> {
|
|
143
|
+
if (input === output) {
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
endIfEqual('abc', 'abc');
|
|
149
|
+
//=> never
|
|
150
|
+
|
|
151
|
+
endIfEqual('abc', '123');
|
|
152
|
+
//=> void
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
@category Type Guard
|
|
156
|
+
@category Utilities
|
|
157
|
+
*/
|
|
158
|
+
export type IsNever<T> = [
|
|
159
|
+
T
|
|
160
|
+
] extends [
|
|
161
|
+
never
|
|
162
|
+
] ? true : false;
|
|
163
|
+
/**
|
|
164
|
+
Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
|
|
165
|
+
|
|
166
|
+
@example
|
|
167
|
+
```
|
|
168
|
+
import type {Simplify} from 'type-fest';
|
|
169
|
+
|
|
170
|
+
type PositionProps = {
|
|
171
|
+
top: number;
|
|
172
|
+
left: number;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
type SizeProps = {
|
|
176
|
+
width: number;
|
|
177
|
+
height: number;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// In your editor, hovering over `Props` will show a flattened object with all the properties.
|
|
181
|
+
type Props = Simplify<PositionProps & SizeProps>;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Sometimes it is desired to pass a value as a function argument that has a different type. At first inspection it may seem assignable, and then you discover it is not because the `value`'s type definition was defined as an interface. In the following example, `fn` requires an argument of type `Record<string, unknown>`. If the value is defined as a literal, then it is assignable. And if the `value` is defined as type using the `Simplify` utility the value is assignable. But if the `value` is defined as an interface, it is not assignable because the interface is not sealed and elsewhere a non-string property could be added to the interface.
|
|
185
|
+
|
|
186
|
+
If the type definition must be an interface (perhaps it was defined in a third-party npm package), then the `value` can be defined as `const value: Simplify<SomeInterface> = ...`. Then `value` will be assignable to the `fn` argument. Or the `value` can be cast as `Simplify<SomeInterface>` if you can't re-declare the `value`.
|
|
187
|
+
|
|
188
|
+
@example
|
|
189
|
+
```
|
|
190
|
+
import type {Simplify} from 'type-fest';
|
|
191
|
+
|
|
192
|
+
interface SomeInterface {
|
|
193
|
+
foo: number;
|
|
194
|
+
bar?: string;
|
|
195
|
+
baz: number | undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type SomeType = {
|
|
199
|
+
foo: number;
|
|
200
|
+
bar?: string;
|
|
201
|
+
baz: number | undefined;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const literal = {foo: 123, bar: 'hello', baz: 456};
|
|
205
|
+
const someType: SomeType = literal;
|
|
206
|
+
const someInterface: SomeInterface = literal;
|
|
207
|
+
|
|
208
|
+
function fn(object: Record<string, unknown>): void {}
|
|
209
|
+
|
|
210
|
+
fn(literal); // Good: literal object type is sealed
|
|
211
|
+
fn(someType); // Good: type is sealed
|
|
212
|
+
fn(someInterface); // Error: Index signature for type 'string' is missing in type 'someInterface'. Because `interface` can be re-opened
|
|
213
|
+
fn(someInterface as Simplify<SomeInterface>); // Good: transform an `interface` into a `type`
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
@link https://github.com/microsoft/TypeScript/issues/15300
|
|
217
|
+
@see SimplifyDeep
|
|
218
|
+
@category Object
|
|
219
|
+
*/
|
|
220
|
+
export type Simplify<T> = {
|
|
221
|
+
[KeyType in keyof T]: T[KeyType];
|
|
222
|
+
} & {};
|
|
223
|
+
/**
|
|
224
|
+
Returns the last element of a union type.
|
|
225
|
+
|
|
226
|
+
@example
|
|
227
|
+
```
|
|
228
|
+
type Last = LastOfUnion<1 | 2 | 3>;
|
|
229
|
+
//=> 3
|
|
230
|
+
```
|
|
231
|
+
*/
|
|
232
|
+
export type LastOfUnion<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
|
|
233
|
+
/**
|
|
234
|
+
Convert a union type into an unordered tuple type of its elements.
|
|
235
|
+
|
|
236
|
+
"Unordered" means the elements of the tuple are not guaranteed to be in the same order as in the union type. The arrangement can appear random and may change at any time.
|
|
237
|
+
|
|
238
|
+
This can be useful when you have objects with a finite set of keys and want a type defining only the allowed keys, but do not want to repeat yourself.
|
|
239
|
+
|
|
240
|
+
@example
|
|
241
|
+
```
|
|
242
|
+
import type {UnionToTuple} from 'type-fest';
|
|
243
|
+
|
|
244
|
+
type Numbers = 1 | 2 | 3;
|
|
245
|
+
type NumbersTuple = UnionToTuple<Numbers>;
|
|
246
|
+
//=> [1, 2, 3]
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
@example
|
|
250
|
+
```
|
|
251
|
+
import type {UnionToTuple} from 'type-fest';
|
|
252
|
+
|
|
253
|
+
const pets = {
|
|
254
|
+
dog: '🐶',
|
|
255
|
+
cat: '🐱',
|
|
256
|
+
snake: '🐍',
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
type Pet = keyof typeof pets;
|
|
260
|
+
//=> 'dog' | 'cat' | 'snake'
|
|
261
|
+
|
|
262
|
+
const petList = Object.keys(pets) as UnionToTuple<Pet>;
|
|
263
|
+
//=> ['dog', 'cat', 'snake']
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
@category Array
|
|
267
|
+
*/
|
|
268
|
+
export type UnionToTuple<T, L = LastOfUnion<T>> = IsNever<T> extends false ? [
|
|
269
|
+
...UnionToTuple<Exclude<T, L>>,
|
|
270
|
+
L
|
|
271
|
+
] : [
|
|
272
|
+
];
|
|
273
|
+
export type GetKey<Value> = {
|
|
274
|
+
[Key in keyof Values]: Value extends Values[Key] ? Key : never;
|
|
275
|
+
}[keyof Values];
|
|
276
|
+
export type GetTypes<Value extends unknown[]> = Value extends [
|
|
277
|
+
infer Single
|
|
278
|
+
] ? Single : Value;
|
|
279
|
+
export type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
|
|
280
|
+
export type InferProperty<Value> = Value extends Property ? Value["type"] extends keyof Values ? Values[Value["type"]] : Value["type"] extends (keyof Values)[] ? Values[Value["type"][number]] : never : never;
|
|
281
|
+
export type InferValue<Value> = Value extends keyof Values ? Values[Value] : Value extends (keyof Values)[] ? Values[Value[number]] : never;
|
|
282
|
+
export type Inferred<Model extends Schema> = {
|
|
283
|
+
[Key in InferredRequiredProperties<Model>]: Model[Key] extends Property ? InferProperty<Model[Key]> : InferValue<Model[Key]>;
|
|
284
|
+
} & {
|
|
285
|
+
[Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property ? InferProperty<Model[Key]> : never;
|
|
286
|
+
};
|
|
287
|
+
export type InferredOptionalProperties<Model extends Schema> = {
|
|
288
|
+
[Key in keyof Model]: Model[Key] extends Property ? Model[Key]["required"] extends false ? Key : never : never;
|
|
289
|
+
}[keyof Model];
|
|
290
|
+
export type InferredRequiredProperties<Model extends Schema> = {
|
|
291
|
+
[Key in keyof Model]: Model[Key] extends Property ? Model[Key]["required"] extends false ? never : Key : Key;
|
|
292
|
+
}[keyof Model];
|
|
293
|
+
export type OptionalProperty<Type> = {
|
|
294
|
+
required: false;
|
|
295
|
+
type: GetType<Type>;
|
|
296
|
+
};
|
|
297
|
+
export type Property = {
|
|
298
|
+
required?: boolean;
|
|
299
|
+
type: keyof Values | (keyof Values)[];
|
|
300
|
+
};
|
|
301
|
+
export type RequiredProperty<Type> = {
|
|
302
|
+
required: true;
|
|
303
|
+
type: GetType<Type>;
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* A schema for validating objects
|
|
307
|
+
*/
|
|
308
|
+
export type Schema = Record<string, keyof Values | (keyof Values)[] | Property>;
|
|
309
|
+
/**
|
|
310
|
+
* A schematic for validating objects
|
|
311
|
+
*/
|
|
312
|
+
export type Schematic<Model> = {
|
|
313
|
+
/**
|
|
314
|
+
* Does the value match the schema?
|
|
315
|
+
*/
|
|
316
|
+
is(value: unknown): value is Model;
|
|
317
|
+
};
|
|
318
|
+
export type Typed = Record<string, unknown>;
|
|
319
|
+
/**
|
|
320
|
+
* A typed schema for validating objects
|
|
321
|
+
*/
|
|
322
|
+
export type TypedSchema<Model extends Typed> = Simplify<{
|
|
323
|
+
[Key in RequiredKeysOf<Model>]: GetType<Model[Key]> | RequiredProperty<Model[Key]>;
|
|
324
|
+
} & {
|
|
325
|
+
[Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
|
|
326
|
+
}>;
|
|
327
|
+
export type Values = {
|
|
328
|
+
array: unknown[];
|
|
329
|
+
bigint: bigint;
|
|
330
|
+
boolean: boolean;
|
|
331
|
+
date: Date;
|
|
332
|
+
function: Function;
|
|
333
|
+
null: null;
|
|
334
|
+
number: number;
|
|
335
|
+
object: object;
|
|
336
|
+
string: string;
|
|
337
|
+
symbol: symbol;
|
|
338
|
+
undefined: undefined;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Create a schematic from a typed schema
|
|
342
|
+
*/
|
|
343
|
+
export declare function schematic<Model extends Typed>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
344
|
+
/**
|
|
345
|
+
* Create a schematic from a schema
|
|
346
|
+
*/
|
|
347
|
+
export declare function schematic<Model extends Schema>(schema: Model): Schematic<Inferred<Model>>;
|
|
348
|
+
|
|
3
349
|
export {};
|
package/types/index.d.ts
CHANGED
|
@@ -1 +1,75 @@
|
|
|
1
|
+
import type { OptionalKeysOf, RequiredKeysOf, Simplify, UnionToTuple } from 'type-fest';
|
|
2
|
+
type GetKey<Value> = {
|
|
3
|
+
[Key in keyof Values]: Value extends Values[Key] ? Key : never;
|
|
4
|
+
}[keyof Values];
|
|
5
|
+
type GetTypes<Value extends unknown[]> = Value extends [infer Single] ? Single : Value;
|
|
6
|
+
type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
|
|
7
|
+
type InferProperty<Value> = Value extends Property ? Value['type'] extends keyof Values ? Values[Value['type']] : Value['type'] extends (keyof Values)[] ? Values[Value['type'][number]] : never : never;
|
|
8
|
+
type InferValue<Value> = Value extends keyof Values ? Values[Value] : Value extends (keyof Values)[] ? Values[Value[number]] : never;
|
|
9
|
+
type Inferred<Model extends Schema> = {
|
|
10
|
+
[Key in InferredRequiredProperties<Model>]: Model[Key] extends Property ? InferProperty<Model[Key]> : InferValue<Model[Key]>;
|
|
11
|
+
} & {
|
|
12
|
+
[Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property ? InferProperty<Model[Key]> : never;
|
|
13
|
+
};
|
|
14
|
+
type InferredOptionalProperties<Model extends Schema> = {
|
|
15
|
+
[Key in keyof Model]: Model[Key] extends Property ? Model[Key]['required'] extends false ? Key : never : never;
|
|
16
|
+
}[keyof Model];
|
|
17
|
+
type InferredRequiredProperties<Model extends Schema> = {
|
|
18
|
+
[Key in keyof Model]: Model[Key] extends Property ? Model[Key]['required'] extends false ? never : Key : Key;
|
|
19
|
+
}[keyof Model];
|
|
20
|
+
type OptionalProperty<Type> = {
|
|
21
|
+
required: false;
|
|
22
|
+
type: GetType<Type>;
|
|
23
|
+
};
|
|
24
|
+
type Property = {
|
|
25
|
+
required?: boolean;
|
|
26
|
+
type: keyof Values | (keyof Values)[];
|
|
27
|
+
};
|
|
28
|
+
type RequiredProperty<Type> = {
|
|
29
|
+
required: true;
|
|
30
|
+
type: GetType<Type>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A schema for validating objects
|
|
34
|
+
*/
|
|
35
|
+
export type Schema = Record<string, keyof Values | (keyof Values)[] | Property>;
|
|
36
|
+
/**
|
|
37
|
+
* A schematic for validating objects
|
|
38
|
+
*/
|
|
39
|
+
export type Schematic<Model> = {
|
|
40
|
+
/**
|
|
41
|
+
* Does the value match the schema?
|
|
42
|
+
*/
|
|
43
|
+
is(value: unknown): value is Model;
|
|
44
|
+
};
|
|
45
|
+
type Typed = Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* A typed schema for validating objects
|
|
48
|
+
*/
|
|
49
|
+
export type TypedSchema<Model extends Typed> = Simplify<{
|
|
50
|
+
[Key in RequiredKeysOf<Model>]: GetType<Model[Key]> | RequiredProperty<Model[Key]>;
|
|
51
|
+
} & {
|
|
52
|
+
[Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
|
|
53
|
+
}>;
|
|
54
|
+
type Values = {
|
|
55
|
+
array: unknown[];
|
|
56
|
+
bigint: bigint;
|
|
57
|
+
boolean: boolean;
|
|
58
|
+
date: Date;
|
|
59
|
+
function: Function;
|
|
60
|
+
null: null;
|
|
61
|
+
number: number;
|
|
62
|
+
object: object;
|
|
63
|
+
string: string;
|
|
64
|
+
symbol: symbol;
|
|
65
|
+
undefined: undefined;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Create a schematic from a typed schema
|
|
69
|
+
*/
|
|
70
|
+
export declare function schematic<Model extends Typed>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
71
|
+
/**
|
|
72
|
+
* Create a schematic from a schema
|
|
73
|
+
*/
|
|
74
|
+
export declare function schematic<Model extends Schema>(schema: Model): Schematic<Inferred<Model>>;
|
|
1
75
|
export {};
|