@oscarpalmer/jhunal 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/constants.d.mts +28 -15
  2. package/dist/constants.mjs +31 -14
  3. package/dist/helpers.d.mts +8 -1
  4. package/dist/helpers.mjs +68 -3
  5. package/dist/index.d.mts +283 -262
  6. package/dist/index.mjs +189 -56
  7. package/dist/models/infer.model.d.mts +66 -0
  8. package/dist/models/infer.model.mjs +1 -0
  9. package/dist/models/misc.model.d.mts +153 -0
  10. package/dist/models/misc.model.mjs +1 -0
  11. package/dist/models/schema.plain.model.d.mts +92 -0
  12. package/dist/models/schema.plain.model.mjs +1 -0
  13. package/dist/models/schema.typed.model.d.mts +96 -0
  14. package/dist/models/schema.typed.model.mjs +1 -0
  15. package/dist/models/transform.model.d.mts +59 -0
  16. package/dist/models/transform.model.mjs +1 -0
  17. package/dist/models/validation.model.d.mts +81 -0
  18. package/dist/models/validation.model.mjs +21 -0
  19. package/dist/schematic.d.mts +20 -6
  20. package/dist/schematic.mjs +7 -12
  21. package/dist/validation/property.validation.d.mts +1 -1
  22. package/dist/validation/property.validation.mjs +21 -17
  23. package/dist/validation/value.validation.d.mts +2 -2
  24. package/dist/validation/value.validation.mjs +63 -11
  25. package/package.json +3 -3
  26. package/src/constants.ts +84 -18
  27. package/src/helpers.ts +162 -4
  28. package/src/index.ts +3 -1
  29. package/src/models/infer.model.ts +105 -0
  30. package/src/models/misc.model.ts +212 -0
  31. package/src/models/schema.plain.model.ts +110 -0
  32. package/src/models/schema.typed.model.ts +109 -0
  33. package/src/models/transform.model.ts +85 -0
  34. package/src/models/validation.model.ts +123 -0
  35. package/src/schematic.ts +29 -18
  36. package/src/validation/property.validation.ts +46 -36
  37. package/src/validation/value.validation.ts +115 -15
  38. package/dist/models.d.mts +0 -507
  39. package/dist/models.mjs +0 -18
  40. package/src/models.ts +0 -691
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/jhunal",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Flies free beneath the glistening moons…",
5
5
  "keywords": [
6
6
  "schema",
@@ -38,7 +38,7 @@
38
38
  "watch": "npx vite build --watch"
39
39
  },
40
40
  "dependencies": {
41
- "@oscarpalmer/atoms": "^0.166"
41
+ "@oscarpalmer/atoms": "^0.169"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.5",
@@ -51,4 +51,4 @@
51
51
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
52
52
  },
53
53
  "packageManager": "npm@11.11.1"
54
- }
54
+ }
package/src/constants.ts CHANGED
@@ -1,42 +1,106 @@
1
- import type {ValueName} from './models';
1
+ import type {ValueName} from './models/misc.model';
2
+ import type {ReportingType} from './models/validation.model';
2
3
 
3
- export const ERROR_NAME = 'SchematicError';
4
-
5
- export const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
4
+ // #region Misc.
6
5
 
7
6
  export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
8
7
 
9
- export const MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
10
-
11
- export const MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED =
12
- "'<key>.<property>' property is not allowed for schemas in $type";
8
+ // #endregion
13
9
 
14
- export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
10
+ // #region Names
15
11
 
16
- export const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
12
+ export const NAME_SCHEMATIC = 'Schematic';
17
13
 
18
- export const MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
14
+ export const NAME_ERROR_SCHEMATIC = 'SchematicError';
19
15
 
20
- export const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
16
+ export const NAME_ERROR_VALIDATION = 'ValidationError';
21
17
 
22
- export const MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
18
+ // #endregion
23
19
 
24
- export const MESSAGE_VALIDATOR_INVALID_VALUE =
25
- "Validator '<>' must be a function or an array of functions";
20
+ // #region Properties
26
21
 
27
22
  export const PROPERTY_REQUIRED = '$required';
28
23
 
24
+ export const PROPERTY_SCHEMATIC = '$schematic';
25
+
29
26
  export const PROPERTY_TYPE = '$type';
30
27
 
31
28
  export const PROPERTY_VALIDATORS = '$validators';
32
29
 
33
- export const SCHEMATIC_NAME = '$schematic';
30
+ // #endregion
31
+
32
+ // #region Property validation
33
+
34
+ export const VALIDATION_MESSAGE_INVALID_INPUT = "Expected 'object' as input but received <>";
35
+
36
+ export const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '<>'";
37
+
38
+ export const VALIDATION_MESSAGE_INVALID_TYPE = "Expected <> for '<>' but received <>";
39
+
40
+ export const VALIDATION_MESSAGE_INVALID_VALUE =
41
+ "Value does not satisfy validator for '<>' and type '<>'";
42
+
43
+ export const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = ' at index <>';
44
+
45
+ // #endregion
46
+
47
+ // #region Reporting
48
+
49
+ export const REPORTING_ALL: ReportingType = 'all';
50
+
51
+ export const REPORTING_FIRST: ReportingType = 'first';
52
+
53
+ export const REPORTING_NONE: ReportingType = 'none';
54
+
55
+ export const REPORTING_THROW: ReportingType = 'throw';
56
+
57
+ export const REPORTING_TYPES = new Set<ReportingType>([
58
+ REPORTING_ALL,
59
+ REPORTING_FIRST,
60
+ REPORTING_NONE,
61
+ REPORTING_THROW,
62
+ ]);
63
+
64
+ // #endregion
65
+
66
+ // #region Schematic validation
67
+
68
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
69
+
70
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED =
71
+ "'<>.<>' property is not allowed for schemas in $type";
72
+
73
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE =
74
+ "'<>' property must not be 'null' or 'undefined'";
75
+
76
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED =
77
+ "'<>.$required' property must be a boolean";
78
+
79
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE =
80
+ "'<>' property must be of a valid type";
81
+
82
+ export const SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
83
+
84
+ export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
85
+
86
+ export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
87
+
88
+ export const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE =
89
+ "Validator '<>' must be a function or an array of functions";
90
+
91
+ // #endregion
92
+
93
+ // #region Templates
34
94
 
35
95
  export const TEMPLATE_PATTERN = '<>';
36
96
 
37
- export const TEMPLATE_PATTERN_KEY = '<key>';
97
+ // #endregion
98
+
99
+ // #region Types
38
100
 
39
- export const TEMPLATE_PATTERN_PROPERTY = '<property>';
101
+ export const TYPE_ARRAY = 'array';
102
+
103
+ export const TYPE_NULL = 'null';
40
104
 
41
105
  export const TYPE_OBJECT = 'object';
42
106
 
@@ -55,3 +119,5 @@ export const VALIDATABLE_TYPES = new Set<ValueName>([
55
119
  ]);
56
120
 
57
121
  export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
122
+
123
+ // #endregion
package/src/helpers.ts CHANGED
@@ -1,8 +1,134 @@
1
- import {isConstructor} from '@oscarpalmer/atoms/is';
1
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {Constructor} from '@oscarpalmer/atoms/models';
3
- import {MESSAGE_CONSTRUCTOR, SCHEMATIC_NAME} from './constants';
3
+ import {
4
+ MESSAGE_CONSTRUCTOR,
5
+ NAME_SCHEMATIC,
6
+ PROPERTY_SCHEMATIC,
7
+ REPORTING_ALL,
8
+ REPORTING_FIRST,
9
+ REPORTING_NONE,
10
+ REPORTING_THROW,
11
+ REPORTING_TYPES,
12
+ TEMPLATE_PATTERN,
13
+ TYPE_ARRAY,
14
+ TYPE_NULL,
15
+ TYPE_OBJECT,
16
+ TYPE_UNDEFINED,
17
+ VALIDATION_MESSAGE_INVALID_INPUT,
18
+ VALIDATION_MESSAGE_INVALID_REQUIRED,
19
+ VALIDATION_MESSAGE_INVALID_TYPE,
20
+ VALIDATION_MESSAGE_INVALID_VALUE,
21
+ VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX,
22
+ } from './constants';
23
+ import type {ValueName} from './models/misc.model';
24
+ import type {
25
+ ReportingInformation,
26
+ ReportingType,
27
+ ValidatedProperty,
28
+ ValidatedPropertyType,
29
+ } from './models/validation.model';
4
30
  import type {Schematic} from './schematic';
5
31
 
32
+ export function getInvalidInputMessage(actual: unknown): string {
33
+ return VALIDATION_MESSAGE_INVALID_INPUT.replace(TEMPLATE_PATTERN, getValueType(actual));
34
+ }
35
+
36
+ export function getInvalidMissingMessage(property: ValidatedProperty): string {
37
+ let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace(
38
+ TEMPLATE_PATTERN,
39
+ renderTypes(property.types),
40
+ );
41
+
42
+ message = message.replace(TEMPLATE_PATTERN, property.key.full);
43
+
44
+ return message;
45
+ }
46
+
47
+ export function getInvalidTypeMessage(property: ValidatedProperty, actual: unknown): string {
48
+ let message = VALIDATION_MESSAGE_INVALID_TYPE.replace(
49
+ TEMPLATE_PATTERN,
50
+ renderTypes(property.types),
51
+ );
52
+
53
+ message = message.replace(TEMPLATE_PATTERN, property.key.full);
54
+ message = message.replace(TEMPLATE_PATTERN, getValueType(actual));
55
+
56
+ return message;
57
+ }
58
+
59
+ export function getInvalidValidatorMessage(
60
+ property: ValidatedProperty,
61
+ type: ValueName,
62
+ index: number,
63
+ length: number,
64
+ ): string {
65
+ let message = VALIDATION_MESSAGE_INVALID_VALUE.replace(TEMPLATE_PATTERN, property.key.full);
66
+
67
+ message = message.replace(TEMPLATE_PATTERN, type);
68
+
69
+ if (length > 1) {
70
+ message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace(TEMPLATE_PATTERN, String(index));
71
+ }
72
+
73
+ return message;
74
+ }
75
+
76
+ function getPropertyType(original: ValidatedPropertyType): string {
77
+ if (typeof original === 'function') {
78
+ return 'a validated value';
79
+ }
80
+
81
+ if (Array.isArray(original)) {
82
+ return `'${TYPE_OBJECT}'`;
83
+ }
84
+
85
+ if (isSchematic(original)) {
86
+ return `a ${NAME_SCHEMATIC}`;
87
+ }
88
+
89
+ return `'${String(original)}'`;
90
+ }
91
+
92
+ export function getReporting(value: unknown): ReportingInformation {
93
+ const type = REPORTING_TYPES.has(value as ReportingType)
94
+ ? (value as ReportingType)
95
+ : REPORTING_NONE;
96
+
97
+ return {
98
+ [REPORTING_ALL]: type === REPORTING_ALL,
99
+ [REPORTING_FIRST]: type === REPORTING_FIRST,
100
+ [REPORTING_NONE]: type === REPORTING_NONE,
101
+ [REPORTING_THROW]: type === REPORTING_THROW,
102
+ } as ReportingInformation;
103
+ }
104
+
105
+ function getValueType(value: unknown): string {
106
+ const valueType = typeof value;
107
+
108
+ switch (true) {
109
+ case value === null:
110
+ return `'${TYPE_NULL}'`;
111
+
112
+ case value === undefined:
113
+ return `'${TYPE_UNDEFINED}'`;
114
+
115
+ case valueType !== TYPE_OBJECT:
116
+ return `'${valueType}'`;
117
+
118
+ case Array.isArray(value):
119
+ return `'${TYPE_ARRAY}'`;
120
+
121
+ case isPlainObject(value):
122
+ return `'${TYPE_OBJECT}'`;
123
+
124
+ case isSchematic(value):
125
+ return `a ${NAME_SCHEMATIC}`;
126
+
127
+ default:
128
+ return value.constructor.name;
129
+ }
130
+ }
131
+
6
132
  /**
7
133
  * Creates a validator function for a given constructor
8
134
  * @param constructor - Constructor to check against
@@ -30,7 +156,39 @@ export function isSchematic(value: unknown): value is Schematic<never> {
30
156
  return (
31
157
  typeof value === 'object' &&
32
158
  value !== null &&
33
- SCHEMATIC_NAME in value &&
34
- value[SCHEMATIC_NAME] === true
159
+ PROPERTY_SCHEMATIC in value &&
160
+ value[PROPERTY_SCHEMATIC] === true
35
161
  );
36
162
  }
163
+
164
+ function renderTypes(types: ValidatedPropertyType[]): string {
165
+ const unique = new Set<string>();
166
+ const parts: string[] = [];
167
+
168
+ for (let index = 0; index < types.length; index += 1) {
169
+ const rendered = getPropertyType(types[index]);
170
+
171
+ if (unique.has(rendered)) {
172
+ continue;
173
+ }
174
+
175
+ unique.add(rendered);
176
+ parts.push(rendered);
177
+ }
178
+
179
+ const {length} = parts;
180
+
181
+ let rendered = '';
182
+
183
+ for (let index = 0; index < length; index += 1) {
184
+ rendered += parts[index];
185
+
186
+ if (index < length - 2) {
187
+ rendered += ', ';
188
+ } else if (index === length - 2) {
189
+ rendered += parts.length > 2 ? ', or ' : ' or ';
190
+ }
191
+ }
192
+
193
+ return rendered;
194
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export {instanceOf, isSchematic} from './helpers';
2
- export {SchematicError, type Schema, type TypedSchema} from './models';
2
+ export type {Schema} from './models/schema.plain.model';
3
+ export type {TypedSchema} from './models/schema.typed.model';
4
+ export {SchematicError, ValidationError} from './models/validation.model';
3
5
  export {schematic, type Schematic} from './schematic';
@@ -0,0 +1,105 @@
1
+ import type {Constructor, Simplify} from '@oscarpalmer/atoms/models';
2
+ import type {Schematic} from '../schematic';
3
+ import type {IsOptionalProperty, ValueName, Values} from './misc.model';
4
+ import type {PlainSchema, Schema, SchemaProperty} from './schema.plain.model';
5
+
6
+ /**
7
+ * Infers the TypeScript type from a {@link Schema} definition
8
+ *
9
+ * @template Model - Schema to infer types from
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const userSchema = {
14
+ * name: 'string',
15
+ * age: 'number',
16
+ * address: { $required: false, $type: 'string' },
17
+ * } satisfies Schema;
18
+ *
19
+ * type User = Infer<typeof userSchema>;
20
+ * // { name: string; age: number; address?: string }
21
+ * ```
22
+ */
23
+ export type Infer<Model extends Schema> = Simplify<
24
+ {
25
+ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
26
+ } & {
27
+ [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
28
+ }
29
+ >;
30
+
31
+ /**
32
+ * Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
33
+ *
34
+ * @template Model - {@link Schema} to extract optional keys from
35
+ */
36
+ export type InferOptionalKeys<Model extends Schema> = keyof {
37
+ [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
38
+ };
39
+
40
+ /**
41
+ * Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
42
+ *
43
+ * @template Value - `$type` value _(single or array)_
44
+ */
45
+ export type InferPropertyType<Value> = Value extends (infer Item)[]
46
+ ? InferPropertyValue<Item>
47
+ : InferPropertyValue<Value>;
48
+
49
+ /**
50
+ * Maps a single type definition to its TypeScript equivalent
51
+ *
52
+ * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
53
+ *
54
+ * @template Value - single type definition
55
+ */
56
+ export type InferPropertyValue<Value> =
57
+ Value extends Constructor<infer Instance>
58
+ ? Instance
59
+ : Value extends Schematic<infer Model>
60
+ ? Model
61
+ : Value extends ValueName
62
+ ? Values[Value & ValueName]
63
+ : Value extends Schema
64
+ ? Infer<Value>
65
+ : never;
66
+
67
+ /**
68
+ * Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
69
+ *
70
+ * @template Model - Schema to extract required keys from
71
+ */
72
+ export type InferRequiredKeys<Model extends Schema> = keyof {
73
+ [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
74
+ };
75
+
76
+ /**
77
+ * Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
78
+ *
79
+ * @template Value - Schema entry value _(single or array)_
80
+ */
81
+ export type InferSchemaEntry<Value> = Value extends (infer Item)[]
82
+ ? InferSchemaEntryValue<Item>
83
+ : InferSchemaEntryValue<Value>;
84
+
85
+ /**
86
+ * Resolves a single schema entry to its TypeScript type
87
+ *
88
+ * Handles, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link NestedSchema} objects, {@link ValueName} strings, and plain {@link Schema} objects
89
+ *
90
+ * @template Value - single schema entry
91
+ */
92
+ export type InferSchemaEntryValue<Value> =
93
+ Value extends Constructor<infer Instance>
94
+ ? Instance
95
+ : Value extends Schematic<infer Model>
96
+ ? Model
97
+ : Value extends SchemaProperty
98
+ ? InferPropertyType<Value['$type']>
99
+ : Value extends PlainSchema
100
+ ? Infer<Value & Schema>
101
+ : Value extends ValueName
102
+ ? Values[Value & ValueName]
103
+ : Value extends Schema
104
+ ? Infer<Value>
105
+ : never;
@@ -0,0 +1,212 @@
1
+ import type {SchemaProperty} from './schema.plain.model';
2
+
3
+ /**
4
+ * Removes duplicate types from a tuple, preserving first occurrence order
5
+ *
6
+ * @template Value - Tuple to deduplicate
7
+ * @template Seen - Accumulator for already-seen types _(internal)_
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // DeduplicateTuple<['string', 'number', 'string']>
12
+ * // => ['string', 'number']
13
+ * ```
14
+ */
15
+ export type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
16
+ infer Head,
17
+ ...infer Tail,
18
+ ]
19
+ ? Head extends Seen[number]
20
+ ? DeduplicateTuple<Tail, Seen>
21
+ : DeduplicateTuple<Tail, [...Seen, Head]>
22
+ : Seen;
23
+
24
+ /**
25
+ * Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
26
+ *
27
+ * @template Value - Type to extract value names from
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // ExtractValueNames<'string'> => 'string'
32
+ * // ExtractValueNames<['string', 'number']> => 'string' | 'number'
33
+ * ```
34
+ */
35
+ export type ExtractValueNames<Value> = Value extends ValueName
36
+ ? Value
37
+ : Value extends (infer Item)[]
38
+ ? ExtractValueNames<Item>
39
+ : Value extends readonly (infer Item)[]
40
+ ? ExtractValueNames<Item>
41
+ : never;
42
+
43
+ /**
44
+ * Determines whether a schema entry is optional
45
+ *
46
+ * Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
47
+ *
48
+ * @template Value - Schema entry to check
49
+ */
50
+ export type IsOptionalProperty<Value> = Value extends SchemaProperty
51
+ ? Value['$required'] extends false
52
+ ? true
53
+ : false
54
+ : false;
55
+
56
+ /**
57
+ * Extracts the last member from a union type by leveraging intersection of function return types
58
+ *
59
+ * @template Value - Union type
60
+ */
61
+ export type LastOfUnion<Value> =
62
+ UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item
63
+ ? Item
64
+ : never;
65
+
66
+ /**
67
+ * Extracts keys from an object type that are optional
68
+ *
69
+ * @template Value - Object type to inspect
70
+ */
71
+ export type OptionalKeys<Value> = {
72
+ [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
73
+ }[keyof Value];
74
+
75
+ /**
76
+ * Extracts keys from an object type that are required _(i.e., not optional)_
77
+ *
78
+ * @template Value - Object type to inspect
79
+ */
80
+ export type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
81
+
82
+ /**
83
+ * Generates all permutations of a tuple type
84
+ *
85
+ * Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
86
+ *
87
+ * @template Tuple - Tuple to permute
88
+ * @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // TuplePermutations<['string', 'number']>
93
+ * // => ['string', 'number'] | ['number', 'string']
94
+ * ```
95
+ */
96
+ export type TuplePermutations<
97
+ Tuple extends unknown[],
98
+ Elput extends unknown[] = [],
99
+ > = Tuple['length'] extends 0
100
+ ? Elput
101
+ : {
102
+ [Key in keyof Tuple]: TuplePermutations<
103
+ TupleRemoveAt<Tuple, Key & `${number}`>,
104
+ [...Elput, Tuple[Key]]
105
+ >;
106
+ }[keyof Tuple & `${number}`];
107
+
108
+ /**
109
+ * Removes the element at a given index from a tuple
110
+ *
111
+ * Used internally by {@link TuplePermutations}
112
+ *
113
+ * @template Items - Tuple to remove from
114
+ * @template Item - Stringified index to remove
115
+ * @template Prefix - Accumulator for elements before the target _(internal)_
116
+ */
117
+ export type TupleRemoveAt<
118
+ Items extends unknown[],
119
+ Item extends string,
120
+ Prefix extends unknown[] = [],
121
+ > = Items extends [infer Head, ...infer Tail]
122
+ ? `${Prefix['length']}` extends Item
123
+ ? [...Prefix, ...Tail]
124
+ : TupleRemoveAt<Tail, Item, [...Prefix, Head]>
125
+ : Prefix;
126
+
127
+ /**
128
+ * Converts a union type into an intersection
129
+ *
130
+ * Uses the contravariance of function parameter types to collapse a union into an intersection
131
+ *
132
+ * @template Value - Union type to convert
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * // UnionToIntersection<{ a: 1 } | { b: 2 }>
137
+ * // => { a: 1 } & { b: 2 }
138
+ * ```
139
+ */
140
+ export type UnionToIntersection<Value> = (
141
+ Value extends unknown ? (value: Value) => void : never
142
+ ) extends (value: infer Item) => void
143
+ ? Item
144
+ : never;
145
+
146
+ /**
147
+ * Converts a union type into an ordered tuple
148
+ *
149
+ * Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
150
+ *
151
+ * @template Value - Union type to convert
152
+ * @template Items - Accumulator for the resulting tuple _(internal)_
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // UnionToTuple<'a' | 'b' | 'c'>
157
+ * // => ['a', 'b', 'c']
158
+ * ```
159
+ */
160
+ export type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
161
+ ? Items
162
+ : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
163
+
164
+ /**
165
+ * Unwraps a single-element tuple to its inner type
166
+ *
167
+ * For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
168
+ *
169
+ * @template Value - Tuple to potentially unwrap
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * // UnwrapSingle<['string']> => 'string'
174
+ * // UnwrapSingle<['string', 'number']> => ['string', 'number'] | ['number', 'string']
175
+ * ```
176
+ */
177
+ export type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
178
+ ? Only
179
+ : Value['length'] extends 1 | 2 | 3 | 4 | 5
180
+ ? TuplePermutations<Value>
181
+ : Value;
182
+
183
+ /**
184
+ * Basic value types
185
+ */
186
+ export type ValueName = keyof Values;
187
+
188
+ /**
189
+ * Maps type name strings to their TypeScript equivalents
190
+ *
191
+ * Used by the type system to resolve {@link ValueName} strings into actual types
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * // Values['string'] => string
196
+ * // Values['date'] => Date
197
+ * // Values['null'] => null
198
+ * ```
199
+ */
200
+ export type Values = {
201
+ array: unknown[];
202
+ bigint: bigint;
203
+ boolean: boolean;
204
+ date: Date;
205
+ function: Function;
206
+ null: null;
207
+ number: number;
208
+ object: object;
209
+ string: string;
210
+ symbol: symbol;
211
+ undefined: undefined;
212
+ };