deep-case-crafter 2.1.1 → 3.1.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.d.cts +397 -186
- package/dist/index.d.mts +397 -186
- package/dist/index.d.ts +397 -186
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +23 -21
- package/readme.md +288 -55
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `true` when `T` is `any`: a conditional type checked against `any` takes both branches,
|
|
3
|
+
* so only `any` turns `true | false` into `boolean`.
|
|
4
|
+
*
|
|
5
|
+
* Two shorter checks are not used. `0 extends 1 & T` is `false` since TypeScript 5.4 for
|
|
6
|
+
* a type parameter whose constraint excludes `1` (such as `To` in `transform`), even when
|
|
7
|
+
* `any` is passed for it. `unknown extends T` is `true` without `strictNullChecks` for an
|
|
8
|
+
* object type whose properties are all optional (such as `{ targetCase?: 'kebab' }`).
|
|
9
|
+
*/
|
|
10
|
+
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
|
11
|
+
/**
|
|
12
|
+
* `true` when `S` is a single string literal such as `'user_id'`, and `false` for
|
|
13
|
+
* `string` and template-literal patterns such as `` `user_${string}` ``.
|
|
14
|
+
*/
|
|
15
|
+
type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
|
|
16
|
+
|
|
1
17
|
/**
|
|
2
18
|
* Represents the supported string case formats for transformation
|
|
3
19
|
*/
|
|
4
20
|
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
21
|
+
/**
|
|
22
|
+
* The plain literal of a target case: a string enum member or a branded literal such as
|
|
23
|
+
* `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
|
|
24
|
+
* detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
|
|
25
|
+
* narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
|
|
26
|
+
*/
|
|
27
|
+
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
5
28
|
|
|
6
29
|
/**
|
|
7
30
|
* Options for the transform function
|
|
@@ -11,250 +34,438 @@ interface TransformOptions {
|
|
|
11
34
|
* The target casing format to transform to
|
|
12
35
|
* @default 'camel'
|
|
13
36
|
*/
|
|
14
|
-
targetCase?: StringCase;
|
|
37
|
+
targetCase?: StringCase | undefined;
|
|
15
38
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* every key's case is auto-detected individually whether or not `sourceCase` is set.
|
|
39
|
+
* How many nesting levels to transform: `1` transforms only the top-level keys, `2`
|
|
40
|
+
* also the keys one level down, and so on. The values of objects, Maps and Sets are one
|
|
41
|
+
* level deeper; array items stay on their array's level. Deeper values are returned
|
|
42
|
+
* as they are.
|
|
21
43
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* non-ASCII (`café_table`) — the runtime preserves the key unchanged, while the type
|
|
26
|
-
* may show it transformed. Set `sourceCase` only when your keys really are that case.
|
|
44
|
+
* Pass a numeric literal to get a precise result type. A depth that is only known to be
|
|
45
|
+
* a `number` (a variable, or `Infinity`) gives an `unknown` result type; omit `depth`
|
|
46
|
+
* to transform every level.
|
|
27
47
|
*
|
|
28
|
-
*
|
|
48
|
+
* @default Infinity
|
|
49
|
+
*/
|
|
50
|
+
depth?: number | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Keys to leave unchanged, each given as its path: the keys of the input that lead to
|
|
53
|
+
* it, joined with `.`. `'metadata.*'` keeps the keys of `metadata`, `'users_by_id.*'`
|
|
54
|
+
* the IDs of a record. `*` stands for any key. Array items and Set members add no key
|
|
55
|
+
* to a path; Map keys do, and `*` also matches a Map key that is not a string. A key
|
|
56
|
+
* that contains a `.` can only be matched by `*`. Only the keys themselves are kept:
|
|
57
|
+
* the values under them are transformed as usual.
|
|
29
58
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* const result = transform(data, { targetCase: 'camel', sourceCase: 'snake' });
|
|
59
|
+
* Pass the paths inline, or `as const`, to get a precise result type. Paths only known
|
|
60
|
+
* as `string[]` give an `unknown` result type.
|
|
33
61
|
*
|
|
34
|
-
* @default
|
|
62
|
+
* @default []
|
|
35
63
|
*/
|
|
36
|
-
|
|
64
|
+
exclude?: readonly string[] | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
67
|
+
* runtime and in the result type, so there is nothing to declare.
|
|
68
|
+
*/
|
|
69
|
+
sourceCase?: StringCase | undefined;
|
|
37
70
|
}
|
|
38
|
-
|
|
39
71
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
72
|
+
* Rejects option names `transform` does not know. The options object is inferred as a
|
|
73
|
+
* whole, so without this an object literal would accept any extra property, typos such
|
|
74
|
+
* as `depht` included. The error names the unknown option.
|
|
42
75
|
*/
|
|
43
|
-
type
|
|
76
|
+
type NoUnknownOptions<Options> = {
|
|
77
|
+
[K in Exclude<keyof Options, keyof TransformOptions>]: `Unknown option: ${K & string}`;
|
|
78
|
+
};
|
|
79
|
+
/** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
|
|
80
|
+
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
44
81
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
82
|
+
* The target case the runtime uses for one options object: `'camel'` when `targetCase`
|
|
83
|
+
* is omitted or `undefined`.
|
|
47
84
|
*/
|
|
48
|
-
type
|
|
49
|
-
infer First extends string,
|
|
50
|
-
...infer Rest extends string[]
|
|
51
|
-
] ? `${Capitalize<First>}${SnakeWordsToPascal<Rest>}` : '';
|
|
52
|
-
type SnakeToWords<S extends string> = S extends `${infer P}_${infer R}` ? [P, ...SnakeToWords<R>] : [S];
|
|
53
|
-
type SnakeToPascal<S extends string> = S extends `${infer P}_${infer R}` ? SnakeWordsToPascal<[P, ...SnakeToWords<R>]> : Capitalize<S>;
|
|
85
|
+
type TargetCaseOf<Options> = 'targetCase' extends keyof Options ? CaseOrDefault<Options['targetCase']> : 'camel';
|
|
54
86
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
87
|
+
* The depth the runtime uses for one options object: `undefined` (every level) when
|
|
88
|
+
* `depth` is omitted.
|
|
57
89
|
*/
|
|
58
|
-
type
|
|
90
|
+
type DepthOf<Options> = 'depth' extends keyof Options ? Options['depth'] : undefined;
|
|
91
|
+
/**
|
|
92
|
+
* The `exclude` paths the runtime uses for one options object: `undefined` (none) when
|
|
93
|
+
* `exclude` is omitted.
|
|
94
|
+
*/
|
|
95
|
+
type ExcludeOf<Options> = 'exclude' extends keyof Options ? Options['exclude'] : undefined;
|
|
96
|
+
|
|
59
97
|
/**
|
|
60
|
-
*
|
|
61
|
-
* conversions. Mirrors `utils/stringFormatters/toSeparatedCase.ts` exactly so the
|
|
62
|
-
* type-level and runtime results never diverge.
|
|
98
|
+
* Type-level mirror of the `exclude` matching (`excludePaths.ts`).
|
|
63
99
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
100
|
+
* The paths that still match at a level are a union of strings: the rest of each
|
|
101
|
+
* `exclude` path below the keys that lead there. `'users_by_id.*' | 'metadata.*'` at the
|
|
102
|
+
* top becomes `'*'` below `users_by_id`. `never` stands for no path.
|
|
67
103
|
*/
|
|
68
|
-
type IsUpper<C extends string> = C extends Uppercase<C> ? (C extends Lowercase<C> ? false : true) : false;
|
|
69
|
-
type IsLower<C extends string> = C extends Lowercase<C> ? (C extends Uppercase<C> ? false : true) : false;
|
|
70
|
-
type IsLowerOrDigit<C extends string> = IsLower<C> extends true ? true : C extends `${number}` ? true : false;
|
|
71
|
-
type FirstChar<S extends string> = S extends `${infer F}${string}` ? F : '';
|
|
72
104
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* `
|
|
105
|
+
* A key type `K` as the paths name its keys: a single key as its string literal, so that
|
|
106
|
+
* a string enum member or a number matches the key a path names, and any other key type
|
|
107
|
+
* (`string`, a branded `string`, a template literal pattern) as it is.
|
|
76
108
|
*/
|
|
77
|
-
type
|
|
78
|
-
|
|
79
|
-
type
|
|
109
|
+
type KeyName<K extends string | number> = K extends string ? IsStringLiteral<K> extends true ? `${K}` : K : `${K}`;
|
|
110
|
+
/** The paths of `Paths` that end at this level: the keys they match keep their names */
|
|
111
|
+
type PathEnds<Paths extends string> = Paths extends `${string}.${string}` ? never : Paths;
|
|
80
112
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
113
|
+
* The rest of each path of `Paths` that continues below the key `Key`, or below every
|
|
114
|
+
* key that no path names when `Key` is `'*'`
|
|
83
115
|
*/
|
|
84
|
-
type
|
|
116
|
+
type PathsBelow<Paths extends string, Key extends string> = Paths extends `${infer Head}.${infer Rest}` ? Head extends Key | '*' ? Rest : never : never;
|
|
85
117
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
118
|
+
* The keys that a key type `K` which is not a single literal (an index signature) may
|
|
119
|
+
* stand for, as far as `Paths` is concerned: each key in it that a path continues below,
|
|
120
|
+
* and `'*'` for every other key. A key is in `K` when they share a value, as `'u1'` does
|
|
121
|
+
* with a branded `string`.
|
|
88
122
|
*/
|
|
89
|
-
type
|
|
123
|
+
type KeyCases<Paths extends string, K extends string> = (Paths extends `${infer Head}.${string}` ? Head extends '*' ? never : [Head & K] extends [never] ? never : Head : never) | '*';
|
|
124
|
+
|
|
90
125
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
126
|
+
* Character classes used by the type-level key transformation. They spell out the
|
|
127
|
+
* ASCII characters explicitly, because TypeScript's `Uppercase`/`Lowercase` are
|
|
128
|
+
* Unicode-aware and would treat a letter like `é` as an ordinary letter.
|
|
93
129
|
*/
|
|
94
|
-
|
|
130
|
+
/** Lowercase ASCII letters */
|
|
131
|
+
type LowerLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
|
|
132
|
+
/** Uppercase ASCII letters */
|
|
133
|
+
type UpperLetter = Uppercase<LowerLetter>;
|
|
134
|
+
/** ASCII digits */
|
|
135
|
+
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
136
|
+
/** The letters of hexadecimal digits */
|
|
137
|
+
type HexLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
|
|
138
|
+
/** Hexadecimal digits, in either case */
|
|
139
|
+
type HexDigit = Digit | HexLetter | Uppercase<HexLetter>;
|
|
140
|
+
/** The word separators of snake_case and kebab-case */
|
|
141
|
+
type Separator = '_' | '-';
|
|
142
|
+
/** Every character a transformable key may contain (the runtime's `A-Z a-z 0-9 _ -`) */
|
|
143
|
+
type KeyChar = LowerLetter | UpperLetter | Digit | Separator;
|
|
144
|
+
|
|
95
145
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
146
|
+
* The plural suffix that stays with the acronym before it instead of starting a new
|
|
147
|
+
* word: `allowedURLs` becomes `allowed_urls`, not `allowed_ur_ls`.
|
|
98
148
|
*/
|
|
99
|
-
|
|
149
|
+
declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
150
|
+
|
|
100
151
|
/**
|
|
101
|
-
* Type
|
|
102
|
-
*
|
|
152
|
+
* Type-level mirror of the runtime key transformation (`transformCase`).
|
|
153
|
+
*
|
|
154
|
+
* Every step below follows a runtime function, so the type of a transformed key is
|
|
155
|
+
* exactly the key the runtime produces. The parity test (`typeRuntimeParity.test.ts`)
|
|
156
|
+
* checks this for thousands of keys.
|
|
103
157
|
*/
|
|
104
|
-
type PascalToSnake<S extends string> = SeparatorCase<S, '_'>;
|
|
105
158
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
159
|
+
* `true` when every character of `S` may appear in a transformable key
|
|
160
|
+
* (tail-recursive, so long keys compile).
|
|
108
161
|
*/
|
|
109
|
-
type
|
|
162
|
+
type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
|
|
163
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`UUID_REGEX`) */
|
|
164
|
+
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
110
165
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
166
|
+
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
167
|
+
* character at a time, stopping at the first character that does not fit, so long keys
|
|
168
|
+
* stay cheap.
|
|
113
169
|
*/
|
|
114
|
-
type
|
|
170
|
+
type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern extends `${infer P}${infer PatternRest}` ? S extends `${infer C}${infer Rest}` ? C extends (P extends 'x' ? HexDigit : P) ? IsUuid<Rest, PatternRest> : false : false : S extends '' ? true : false;
|
|
115
171
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
172
|
+
* Keys the runtime leaves unchanged (`prepareStringTransformation`): keys containing a
|
|
173
|
+
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
174
|
+
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
175
|
+
* keys that the other rules do not preserve are compared with the UUID form.
|
|
118
176
|
*/
|
|
119
|
-
type
|
|
120
|
-
infer First extends string,
|
|
121
|
-
...infer Rest extends string[]
|
|
122
|
-
] ? `${Capitalize<First>}${KebabWordsToPascal<Rest>}` : '';
|
|
123
|
-
type KebabToWords<S extends string> = S extends `${infer P}-${infer R}` ? [P, ...KebabToWords<R>] : [S];
|
|
177
|
+
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
124
178
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
179
|
+
* The case the runtime detects for a key that is not preserved (`detectStringCase`), or
|
|
180
|
+
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
181
|
+
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
127
182
|
*/
|
|
128
|
-
type
|
|
183
|
+
type DetectedCase<K extends string> = K extends `${string}${Separator}${string}` ? K extends Lowercase<K> ? K extends `${string}-${string}` ? K extends `${string}_${string}` | `${string}--${string}` ? 'none' : 'kebab' : K extends `${string}__${string}` ? 'none' : 'snake' : 'none' : K extends `${infer First}${infer Rest}` ? Rest extends Lowercase<Rest> ? 'single' : First extends LowerLetter ? 'camel' : K extends Uppercase<K> ? 'none' : 'pascal' : 'none';
|
|
184
|
+
/** The first character of `S`, or `''` */
|
|
185
|
+
type FirstChar<S extends string> = S extends `${infer C}${string}` ? C : '';
|
|
129
186
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
187
|
+
* Whether a new word starts at `C`, followed by `Rest` (`isWordBoundary`): `C` is
|
|
188
|
+
* uppercase and follows a lowercase letter or digit, or it follows an uppercase letter
|
|
189
|
+
* and comes before a lowercase letter, so the capitals before it are an acronym or a
|
|
190
|
+
* one-letter word — unless that lowercase letter is a plural `s` not followed by another
|
|
191
|
+
* lowercase letter.
|
|
132
192
|
*/
|
|
133
|
-
type
|
|
193
|
+
type IsWordBoundary<C extends string, Prev extends string, Rest extends string> = C extends UpperLetter ? Prev extends LowerLetter | Digit ? true : Prev extends UpperLetter ? Rest extends `${infer Next}${infer AfterNext}` ? Next extends LowerLetter ? Next extends typeof ACRONYM_PLURAL_SUFFIX ? FirstChar<AfterNext> extends LowerLetter ? true : false : true : false : false : false : false;
|
|
194
|
+
/** camelCase/PascalCase to snake_case or kebab-case, acronym-aware (`toSeparatedCase`) */
|
|
195
|
+
type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? ToSeparatedCase<Rest, Sep, C, `${Acc}${IsWordBoundary<C, Prev, Rest> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
|
|
134
196
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
197
|
+
* PascalCase to camelCase (`pascalToCamelCase`): lowercases the first word, up to the
|
|
198
|
+
* first word boundary, so a leading acronym is lowercased as a whole
|
|
199
|
+
* (`HTTPRequest` -> `httpRequest`) and a leading one-letter word on its own
|
|
200
|
+
* (`XAxis` -> `xAxis`)
|
|
138
201
|
*/
|
|
139
|
-
type
|
|
140
|
-
|
|
202
|
+
type PascalToCamelCase<S extends string, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, Rest> extends true ? `${Acc}${S}` : PascalToCamelCase<Rest, C, `${Acc}${Lowercase<C>}`> : Acc;
|
|
203
|
+
/** snake_case/kebab-case to camelCase (`snakeToCamelCase`, `kebabToCamelCase`) */
|
|
204
|
+
type SeparatedToCamelCase<S extends string, Sep extends Separator, Acc extends string = ''> = S extends `${infer Word}${Sep}${infer Next}${infer Rest}` ? SeparatedToCamelCase<Rest, Sep, `${Acc}${Word}${Uppercase<Next>}`> : `${Acc}${S}`;
|
|
205
|
+
/** Replaces every `From` separator with `To` (`snakeToKebabCase`, `kebabToSnakeCase`) */
|
|
206
|
+
type ReplaceSeparator<S extends string, From extends Separator, To extends Separator, Acc extends string = ''> = S extends `${infer Head}${From}${infer Rest}` ? ReplaceSeparator<Rest, From, To, `${Acc}${Head}${To}`> : `${Acc}${S}`;
|
|
207
|
+
/** A single word in the target case (`transformSingleWord`) */
|
|
208
|
+
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
|
|
209
|
+
/** A key of the detected case `From` in the target case (`formatterMap`) */
|
|
210
|
+
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
211
|
+
/** Applies the detected case of `K` (`transformCase`) */
|
|
212
|
+
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
141
213
|
/**
|
|
142
|
-
* The
|
|
143
|
-
*
|
|
144
|
-
* wholesale at runtime (`/[^A-Za-z0-9_-]/`), so the type must preserve it too.
|
|
145
|
-
* An explicit ASCII set is required because TS's `Uppercase`/`Lowercase` are
|
|
146
|
-
* Unicode-aware and would otherwise treat `é` as an ordinary transformable letter.
|
|
214
|
+
* The key a string key `K` becomes when transformed to `To`. A `string` or
|
|
215
|
+
* template-literal key cannot be known exactly, so it becomes `string`.
|
|
147
216
|
*/
|
|
148
|
-
type
|
|
217
|
+
type TransformKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
|
|
218
|
+
|
|
219
|
+
/** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
|
|
220
|
+
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
149
221
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
222
|
+
* Values the runtime returns untouched that TypeScript can recognise: functions and
|
|
223
|
+
* classes, `Date`, `RegExp`, and built-ins that declare a `Symbol.toStringTag` (typed
|
|
224
|
+
* arrays, `ArrayBuffer`, `DataView`, `Promise`, `WeakMap`, `WeakSet`, ...).
|
|
225
|
+
*
|
|
226
|
+
* `Error`, `URL` and other host objects are returned untouched as well, but their types
|
|
227
|
+
* cannot be told apart from plain objects (an `Error` is any `{ name, message }`).
|
|
152
228
|
*/
|
|
153
|
-
type
|
|
154
|
-
|
|
229
|
+
type UntouchedValue = Function | Date | RegExp | {
|
|
230
|
+
readonly [Symbol.toStringTag]: string;
|
|
231
|
+
};
|
|
155
232
|
/**
|
|
156
|
-
*
|
|
233
|
+
* `true` once `Level` (the nesting level so far, as a tuple) reaches `Depth`.
|
|
234
|
+
* `Depth` is `undefined` when every level is transformed. The `undefined` check comes
|
|
235
|
+
* first and is not distributive, so it also holds without `strictNullChecks`; comparing
|
|
236
|
+
* `Depth extends Level['length']` also accepts a branded depth such as `1 & Brand`.
|
|
157
237
|
*/
|
|
158
|
-
type
|
|
238
|
+
type IsDepthReached<Depth extends number | undefined, Level extends unknown[]> = [Depth] extends [undefined] ? false : Depth extends Level['length'] ? true : false;
|
|
159
239
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
240
|
+
* The nesting level of the values of an object, Map or Set. With unlimited depth the
|
|
241
|
+
* level stays `[]`, so recursive data types reuse the same instantiation.
|
|
162
242
|
*/
|
|
163
|
-
type
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
type CamelToSnakeRecursive<T> = T extends Array<infer U> ? Array<CamelToSnakeRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? CamelToSnake<K> : K, CamelToSnakeRecursive<V>> : T extends Set<infer V> ? Set<CamelToSnakeRecursive<V>> : T extends object ? {
|
|
182
|
-
[K in keyof T as TransformKey<K & string, {
|
|
183
|
-
sourceCase: 'camel';
|
|
184
|
-
targetCase: 'snake';
|
|
185
|
-
}>]: CamelToSnakeRecursive<T[K]>;
|
|
186
|
-
} : T;
|
|
187
|
-
type CamelToPascalRecursive<T> = T extends Array<infer U> ? Array<CamelToPascalRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? CamelToPascal<K> : K, CamelToPascalRecursive<V>> : T extends Set<infer V> ? Set<CamelToPascalRecursive<V>> : T extends object ? {
|
|
188
|
-
[K in keyof T as TransformKey<K & string, {
|
|
189
|
-
sourceCase: 'camel';
|
|
190
|
-
targetCase: 'pascal';
|
|
191
|
-
}>]: CamelToPascalRecursive<T[K]>;
|
|
192
|
-
} : T;
|
|
193
|
-
type CamelToKebabRecursive<T> = T extends Array<infer U> ? Array<CamelToKebabRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? CamelToKebab<K> : K, CamelToKebabRecursive<V>> : T extends Set<infer V> ? Set<CamelToKebabRecursive<V>> : T extends object ? {
|
|
194
|
-
[K in keyof T as TransformKey<K & string, {
|
|
195
|
-
sourceCase: 'camel';
|
|
196
|
-
targetCase: 'kebab';
|
|
197
|
-
}>]: CamelToKebabRecursive<T[K]>;
|
|
198
|
-
} : T;
|
|
199
|
-
type PascalToCamelRecursive<T> = T extends Array<infer U> ? Array<PascalToCamelRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? PascalToCamel<K> : K, PascalToCamelRecursive<V>> : T extends Set<infer V> ? Set<PascalToCamelRecursive<V>> : T extends object ? {
|
|
200
|
-
[K in keyof T as TransformKey<K & string, {
|
|
201
|
-
sourceCase: 'pascal';
|
|
202
|
-
targetCase: 'camel';
|
|
203
|
-
}>]: PascalToCamelRecursive<T[K]>;
|
|
204
|
-
} : T;
|
|
205
|
-
type PascalToSnakeRecursive<T> = T extends Array<infer U> ? Array<PascalToSnakeRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? PascalToSnake<K> : K, PascalToSnakeRecursive<V>> : T extends Set<infer V> ? Set<PascalToSnakeRecursive<V>> : T extends object ? {
|
|
206
|
-
[K in keyof T as TransformKey<K & string, {
|
|
207
|
-
sourceCase: 'pascal';
|
|
208
|
-
targetCase: 'snake';
|
|
209
|
-
}>]: PascalToSnakeRecursive<T[K]>;
|
|
210
|
-
} : T;
|
|
211
|
-
type PascalToKebabRecursive<T> = T extends Array<infer U> ? Array<PascalToKebabRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? PascalToKebab<K> : K, PascalToKebabRecursive<V>> : T extends Set<infer V> ? Set<PascalToKebabRecursive<V>> : T extends object ? {
|
|
212
|
-
[K in keyof T as TransformKey<K & string, {
|
|
213
|
-
sourceCase: 'pascal';
|
|
214
|
-
targetCase: 'kebab';
|
|
215
|
-
}>]: PascalToKebabRecursive<T[K]>;
|
|
216
|
-
} : T;
|
|
217
|
-
type KebabToCamelRecursive<T> = T extends Array<infer U> ? Array<KebabToCamelRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? KebabToCamel<K> : K, KebabToCamelRecursive<V>> : T extends Set<infer V> ? Set<KebabToCamelRecursive<V>> : T extends object ? {
|
|
218
|
-
[K in keyof T as TransformKey<K & string, {
|
|
219
|
-
sourceCase: 'kebab';
|
|
220
|
-
targetCase: 'camel';
|
|
221
|
-
}>]: KebabToCamelRecursive<T[K]>;
|
|
222
|
-
} : T;
|
|
223
|
-
type KebabToPascalRecursive<T> = T extends Array<infer U> ? Array<KebabToPascalRecursive<U>> : T extends Map<infer K, infer V> ? Map<K extends string ? KebabToPascal<K> : K, KebabToPascalRecursive<V>> : T extends Set<infer V> ? Set<KebabToPascalRecursive<V>> : T extends object ? {
|
|
224
|
-
[K in keyof T as TransformKey<K & string, {
|
|
225
|
-
sourceCase: 'kebab';
|
|
226
|
-
targetCase: 'pascal';
|
|
227
|
-
}>]: KebabToPascalRecursive<T[K]>;
|
|
243
|
+
type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
|
|
244
|
+
Depth
|
|
245
|
+
] extends [undefined] ? Level : [...Level, unknown];
|
|
246
|
+
/**
|
|
247
|
+
* Transforms `T` the way `internalTransform` does: arrays keep their nesting level,
|
|
248
|
+
* while the values of objects, Maps and Sets are one level deeper.
|
|
249
|
+
*
|
|
250
|
+
* Arrays are typed as arrays of transformed items, which lets TypeScript defer
|
|
251
|
+
* recursive types such as a JSON value type; only tuples are mapped position by position.
|
|
252
|
+
*
|
|
253
|
+
* Use `TransformResult` instead. This type is exported only so that declaration files
|
|
254
|
+
* can refer to it: the result of a generic wrapper around `transform` is written in terms
|
|
255
|
+
* of it, and an unexported recursive type would have to be expanded there without end.
|
|
256
|
+
*/
|
|
257
|
+
type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
|
|
258
|
+
[I in keyof T]: TransformValue<T[I], To, Depth, Level>;
|
|
259
|
+
} : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? {
|
|
260
|
+
[K in keyof T as K extends string ? TransformKey<K, To> : K]: K extends symbol ? T[K] : TransformValue<T[K], To, Depth, NextLevel<Depth, Level>>;
|
|
228
261
|
} : T;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
262
|
+
/**
|
|
263
|
+
* `TransformValue` while `exclude` paths still match: `Paths` is the rest of each of them
|
|
264
|
+
* below this level (see `excludeTypes.ts`). A key at which a path ends keeps its name,
|
|
265
|
+
* and the paths that continue below a key go on into its value. Arrays and Sets pass
|
|
266
|
+
* them on to their items, which add no key to a path. Values that no path reaches are
|
|
267
|
+
* transformed by `TransformValue`.
|
|
268
|
+
*
|
|
269
|
+
* Use `TransformResult` instead. This type is exported for the same reason as
|
|
270
|
+
* `TransformValue`.
|
|
271
|
+
*/
|
|
272
|
+
type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
|
|
273
|
+
[I in keyof T]: TransformValueExcluding<T[I], To, Depth, Level, Paths>;
|
|
274
|
+
} : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformValueExcluding<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
|
|
275
|
+
[K in keyof T as ExcludingKey<K, To, PathEnds<Paths>>]: K extends symbol ? T[K] : TransformBelowKey<T[K], To, Depth, NextLevel<Depth, Level>, Paths, KeyName<K & (string | number)>>;
|
|
234
276
|
} : T;
|
|
235
277
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
278
|
+
* A key of an object, or a member of a Map's key type: it keeps its name where a path
|
|
279
|
+
* of `Ends` ends at it, and is transformed otherwise. A key type that is not a single
|
|
280
|
+
* literal keeps its name only where `*` ends.
|
|
238
281
|
*/
|
|
239
|
-
type
|
|
282
|
+
type ExcludingKey<K, To extends StringCase, Ends extends string> = K extends string ? '*' extends Ends ? K : IsStringLiteral<K> extends true ? [KeyName<K> & Ends] extends [never] ? TransformKey<K, To> : K : TransformKey<K, To> : K;
|
|
283
|
+
/** `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left */
|
|
284
|
+
type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
|
|
285
|
+
/**
|
|
286
|
+
* The value `T` under the key `Key`. A key type that is not a single literal may stand
|
|
287
|
+
* for several keys, which the paths may treat differently: each gives its own result.
|
|
288
|
+
*/
|
|
289
|
+
type TransformBelowKey<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string, Key extends string> = IsStringLiteral<Key> extends true ? TransformBelow<T, To, Depth, Level, PathsBelow<Paths, Key>> : TransformBelowEach<T, To, Depth, Level, Paths, KeyCases<Paths, Key>>;
|
|
290
|
+
/** `T` under each of `Keys` (distributes over them) */
|
|
291
|
+
type TransformBelowEach<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string, Keys extends string> = Keys extends unknown ? TransformBelow<T, To, Depth, Level, PathsBelow<Paths, Keys>> : never;
|
|
292
|
+
/**
|
|
293
|
+
* The paths below the keys of a Map with key type `K`, one tuple per set of them: a Map
|
|
294
|
+
* has one value type for all of its keys, while the paths below each key may differ.
|
|
295
|
+
* Only `*` matches a key that is not a string, and a key type that may also hold strings
|
|
296
|
+
* (`unknown`, `{}`) stands for every string key as well.
|
|
297
|
+
*/
|
|
298
|
+
type MapPathsBelow<K, Paths extends string> = K extends string ? IsStringLiteral<K> extends true ? [PathsBelow<Paths, KeyName<K>>] : EachPathsBelow<Paths, KeyCases<Paths, K>> : string extends K ? EachPathsBelow<Paths, KeyCases<Paths, string>> : [PathsBelow<Paths, '*'>];
|
|
299
|
+
/** The paths below each of `Keys`, as a tuple each (distributes over them) */
|
|
300
|
+
type EachPathsBelow<Paths extends string, Keys extends string> = Keys extends unknown ? [PathsBelow<Paths, Keys>] : never;
|
|
301
|
+
/** The value type of a Map, below each set of paths of `Below` (see `MapPathsBelow`) */
|
|
302
|
+
type TransformBelowMapKeys<V, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Below> = Below extends [infer Paths extends string] ? TransformBelow<V, To, Depth, Level, Paths> : never;
|
|
303
|
+
/**
|
|
304
|
+
* Applies a numeric literal `Depth`: a negative depth transforms nothing, and a
|
|
305
|
+
* fractional depth is not supported precisely, so its result is `unknown`.
|
|
306
|
+
*/
|
|
307
|
+
type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValue<T, To, Depth, []>;
|
|
308
|
+
/**
|
|
309
|
+
* `true` for a numeric literal, branded or not (`2`, `2 & Brand`), and `false` for
|
|
310
|
+
* `number` and a branded `number`: a literal shares no value with at least one of two
|
|
311
|
+
* different literals, while `number` shares one with both.
|
|
312
|
+
*/
|
|
313
|
+
type IsNumberLiteral<N extends number> = [N & 0] extends [never] ? true : [N & 1] extends [never] ? true : false;
|
|
314
|
+
/**
|
|
315
|
+
* The result for one `Depth` (distributes over a union of depths): every level for
|
|
316
|
+
* `undefined`, `unknown` for a depth that is only known to be a `number`.
|
|
317
|
+
*/
|
|
318
|
+
type TransformToDepth<T, To extends StringCase, Depth extends number | undefined> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValue<T, To, undefined, []> : IsNumberLiteral<Depth & number> extends true ? TransformWithDepth<T, To, Depth & number> : unknown : never;
|
|
319
|
+
/**
|
|
320
|
+
* `TransformToDepth` (and `TransformWithDepth`) for the `exclude` paths `Paths`. Kept
|
|
321
|
+
* apart, so that results without `exclude` take no extra step.
|
|
322
|
+
*/
|
|
323
|
+
type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueExcluding<T, To, Depth & number, [], Paths> : unknown : never;
|
|
324
|
+
/**
|
|
325
|
+
* The result for an `exclude` tuple, collecting its paths into `Paths`. A union element
|
|
326
|
+
* stands for several tuples, which give their own results. A path with an empty key makes
|
|
327
|
+
* `transform` throw (`never`), and paths that are not all known (`string[]`, a rest
|
|
328
|
+
* element, a `string` element) give `unknown`.
|
|
329
|
+
*/
|
|
330
|
+
type TransformExcluding<T, To extends StringCase, Depth extends number | undefined, Exclude extends readonly string[], Paths extends string = never> = Exclude extends readonly [] ? [Paths] extends [never] ? TransformToDepth<T, To, Depth> : TransformToDepthExcluding<T, To, Depth, Paths> : Exclude extends readonly [
|
|
331
|
+
infer Path extends string,
|
|
332
|
+
...infer Rest extends readonly string[]
|
|
333
|
+
] ? Path extends unknown ? IsStringLiteral<Path> extends true ? Path extends '' | `.${string}` | `${string}.` | `${string}..${string}` ? never : TransformExcluding<T, To, Depth, Rest, Paths | Path> : unknown : never : unknown;
|
|
334
|
+
/**
|
|
335
|
+
* Includes `true` when a member of `Exclude` is an array that is not a tuple (`string[]`,
|
|
336
|
+
* or a tuple with a rest element), or when `Exclude` is `any`: paths that cannot be known.
|
|
337
|
+
* Their result is `unknown`, and so is the result of a union that includes them, so
|
|
338
|
+
* checking this first saves working out the other members.
|
|
339
|
+
*/
|
|
340
|
+
type HasUnknownPaths<Exclude> = Exclude extends readonly string[] ? number extends Exclude['length'] ? true : false : false;
|
|
341
|
+
/**
|
|
342
|
+
* The result for each member of `Exclude` (distributes over them): no path for an
|
|
343
|
+
* `undefined` one.
|
|
344
|
+
*/
|
|
345
|
+
type TransformExcludingEach<T, To extends StringCase, Depth extends number | undefined, Exclude extends readonly string[] | undefined> = true extends HasUnknownPaths<Exclude> ? unknown : Exclude extends readonly string[] ? TransformExcluding<T, To, Depth, Exclude> : TransformToDepth<T, To, Depth>;
|
|
346
|
+
/**
|
|
347
|
+
* The exact type `transform` returns for `data` of type `T`, transformed to `To` (default
|
|
348
|
+
* `'camel'`) for `Depth` nesting levels (default: all of them), with the keys at the
|
|
349
|
+
* `Exclude` paths left unchanged (the type of the `exclude` option; default: none).
|
|
350
|
+
*
|
|
351
|
+
* `To`, `Depth` and `Exclude` distribute over unions: when they are only known to be one
|
|
352
|
+
* of several values, the result is the union of the possible results. A depth that is not
|
|
353
|
+
* a numeric literal (`number`, including `Infinity`) cannot be known, so the result is
|
|
354
|
+
* `unknown`, as it is for paths that are not a tuple of string literals (`string[]`), and
|
|
355
|
+
* for a `To`, `Depth` or `Exclude` typed `any`. String enum members and branded literals
|
|
356
|
+
* are accepted for `To`.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* type User = TransformResult<{ user_id: number; created_at: Date }>;
|
|
361
|
+
* // => { userId: number; createdAt: Date }
|
|
362
|
+
*
|
|
363
|
+
* type Page = TransformResult<
|
|
364
|
+
* { page_items: Record<string, { item_id: number }> },
|
|
365
|
+
* 'camel',
|
|
366
|
+
* undefined,
|
|
367
|
+
* ['page_items.*']
|
|
368
|
+
* >;
|
|
369
|
+
* // => { pageItems: { [x: string]: { itemId: number } } }
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
type TransformResult<T, To extends StringCase = 'camel', Depth extends number | undefined = undefined, Exclude extends readonly string[] | undefined = undefined> = IsAny<To | Depth | Exclude> extends true ? unknown : [Exclude] extends [undefined] ? To extends unknown ? TransformToDepth<T, PlainCase<To>, Depth> : never : To extends unknown ? TransformExcludingEach<T, PlainCase<To>, Depth, Exclude> : never;
|
|
373
|
+
/** `true` when `Options` may omit `Key` (an optional property) */
|
|
374
|
+
type IsOptional<Options, Key extends PropertyKey> = Key extends keyof Options ? Record<never, never> extends Pick<Options, Key> ? true : false : false;
|
|
375
|
+
/** `true` when the option `Key` of `Options` is typed `any` */
|
|
376
|
+
type IsAnyOption<Options, Key extends PropertyKey> = Key extends keyof Options ? IsAny<Options[Key]> : false;
|
|
377
|
+
/**
|
|
378
|
+
* Includes `true` when a member of a union of options objects has a `targetCase` typed
|
|
379
|
+
* `any` (check it with `true extends HasAnyTargetCase<Options>`)
|
|
380
|
+
*/
|
|
381
|
+
type HasAnyTargetCase<Options> = Options extends unknown ? IsAnyOption<Options, 'targetCase'> : never;
|
|
382
|
+
/**
|
|
383
|
+
* The result for one options object, with the `exclude` paths `Exclude`. An optional
|
|
384
|
+
* `targetCase` or `depth` may also be omitted, which uses its default; those results are
|
|
385
|
+
* added explicitly because, without `strictNullChecks`, `undefined` disappears from the
|
|
386
|
+
* property's type.
|
|
387
|
+
*/
|
|
388
|
+
type TransformResultForOne<T, Options, Exclude extends readonly string[] | undefined = undefined, To extends StringCase = TargetCaseOf<Options>, Depth extends number | undefined = DepthOf<Options> extends infer D extends number | undefined ? D : never> = TransformResult<T, To, Depth, Exclude> | (IsOptional<Options, 'targetCase'> extends true ? TransformResult<T, 'camel', Depth, Exclude> : never) | (IsOptional<Options, 'depth'> extends true ? TransformResult<T, To, undefined, Exclude> : never) | (IsOptional<Options, 'targetCase'> extends true ? IsOptional<Options, 'depth'> extends true ? TransformResult<T, 'camel', undefined, Exclude> : never : never);
|
|
389
|
+
/**
|
|
390
|
+
* The result for one options object with an `exclude` option, which may also be omitted
|
|
391
|
+
* when it is optional (see `TransformResultForOne`). Options whose paths cannot be known,
|
|
392
|
+
* such as `TransformOptions` itself, give `unknown` straight away.
|
|
393
|
+
*
|
|
394
|
+
* Use `TransformResult` instead. This type is exported only so that declaration files
|
|
395
|
+
* can refer to it, as they do to `TransformResultFor`: the result of a generic wrapper
|
|
396
|
+
* whose `exclude` paths are a type parameter is written in terms of it.
|
|
397
|
+
*/
|
|
398
|
+
type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer Exclude extends readonly string[] | undefined ? true extends HasUnknownPaths<Exclude> ? unknown : TransformResultForOne<T, Options, Exclude> | (IsOptional<Options, 'exclude'> extends true ? TransformResultForOne<T, Options> : never) : never;
|
|
399
|
+
/**
|
|
400
|
+
* The result type for an options object. Each member of a union of options objects gives
|
|
401
|
+
* its own result, so its `targetCase`, `depth` and `exclude` stay paired. Options typed
|
|
402
|
+
* `any`, or with a `targetCase`, `depth` or `exclude` typed `any`, say nothing about the
|
|
403
|
+
* result: `unknown`.
|
|
404
|
+
*/
|
|
405
|
+
type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : 'exclude' extends keyof Options ? TransformResultForExcluding<T, Options> : TransformResultForOne<T, Options> : never;
|
|
240
406
|
|
|
241
|
-
|
|
242
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Transforms the keys of `data` to camelCase, at every nesting level.
|
|
409
|
+
*
|
|
410
|
+
* Every key of the result type is typed the way the runtime transforms it, except in
|
|
411
|
+
* values TypeScript cannot tell apart from plain objects (see "Result types" in the
|
|
412
|
+
* readme).
|
|
413
|
+
*/
|
|
414
|
+
declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
|
|
415
|
+
/**
|
|
416
|
+
* Transforms the keys of `data` to `targetCase` (default `'camel'`), at every nesting
|
|
417
|
+
* level unless `depth` is set, except the keys at the `exclude` paths.
|
|
418
|
+
*
|
|
419
|
+
* Every key of the result type is typed the way the runtime transforms it, except in
|
|
420
|
+
* values TypeScript cannot tell apart from plain objects (see "Result types" in the
|
|
421
|
+
* readme).
|
|
422
|
+
*
|
|
423
|
+
* `To` and `Depth` capture the literal values of `targetCase` and `depth`. `Options`
|
|
424
|
+
* captures the options object as a whole, so a value that may be `undefined` (which the
|
|
425
|
+
* runtime treats as omitted) is reflected in the result type, and each member of a union
|
|
426
|
+
* of options objects gets its own result. `NoUnknownOptions` keeps rejecting unknown
|
|
427
|
+
* option names. An options object that may itself be `undefined` matches neither this
|
|
428
|
+
* overload nor the one above, and gets an `unknown` result type from the last one.
|
|
429
|
+
*
|
|
430
|
+
* The constraint of `Options` makes it capture an inline `exclude` array as a tuple of
|
|
431
|
+
* string literals: `[...Paths]` keeps the strings literal, and `readonly [string?]`
|
|
432
|
+
* makes TypeScript 4.7 read the array as a tuple, an empty one included; any array of
|
|
433
|
+
* strings is still accepted, and so is `undefined`: with `exactOptionalPropertyTypes`, an
|
|
434
|
+
* optional property only accepts it when its type includes it. The parameter type does
|
|
435
|
+
* not mention `exclude` itself: intersecting the captured tuple with these types would
|
|
436
|
+
* make every call compare array types member by member.
|
|
437
|
+
*
|
|
438
|
+
* The type parameter defaults only apply when type arguments are passed explicitly, and
|
|
439
|
+
* give an `unknown` result type. An explicit `any` as the second type argument was the
|
|
440
|
+
* output type before `To` existed, so it still gives `any` (`To` is `any` there, while
|
|
441
|
+
* the options keep their default type); a `targetCase` typed `any` gives `unknown`.
|
|
442
|
+
*/
|
|
443
|
+
declare function transform<T, To extends StringCase | undefined = StringCase | undefined, Depth extends number | undefined = number | undefined, Options extends TransformOptions & {
|
|
444
|
+
exclude?: readonly [...Paths] | readonly [string?] | readonly string[] | undefined;
|
|
445
|
+
} = TransformOptions, Paths extends readonly string[] = readonly string[]>(data: T, options: Options & {
|
|
446
|
+
targetCase?: To;
|
|
447
|
+
depth?: Depth;
|
|
448
|
+
} & NoUnknownOptions<Options>): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
|
|
449
|
+
/**
|
|
450
|
+
* The 2.x form with explicit `<Data, SourceCase, TargetCase>` type arguments.
|
|
451
|
+
*
|
|
452
|
+
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
453
|
+
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
454
|
+
* accept `depth` or `exclude`.
|
|
455
|
+
*/
|
|
456
|
+
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
243
457
|
sourceCase: From;
|
|
244
458
|
targetCase: To;
|
|
245
|
-
|
|
459
|
+
depth?: undefined;
|
|
460
|
+
exclude?: undefined;
|
|
461
|
+
}): TransformResult<T, To>;
|
|
246
462
|
/**
|
|
247
463
|
* Transform data to a specified output type.
|
|
248
464
|
*
|
|
249
465
|
* Note: When using this overload, type safety is not enforced between the input, options, and output type.
|
|
250
|
-
* The 'sourceCase' option is
|
|
251
|
-
*/
|
|
252
|
-
declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>
|
|
253
|
-
depth?: number;
|
|
254
|
-
}): K;
|
|
255
|
-
declare function transform<T>(data: T, options?: TransformOptions & {
|
|
256
|
-
depth?: number;
|
|
257
|
-
}): unknown;
|
|
466
|
+
* The deprecated 'sourceCase' option is not accepted here, as it has no effect.
|
|
467
|
+
*/
|
|
468
|
+
declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
|
|
258
469
|
|
|
259
470
|
export { transform };
|
|
260
|
-
export type { StringCase, TransformOptions };
|
|
471
|
+
export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|