deep-case-crafter 3.1.0 → 3.3.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 +120 -19
- package/dist/index.d.mts +120 -19
- package/dist/index.d.ts +120 -19
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/readme.md +88 -13
package/dist/index.d.cts
CHANGED
|
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
|
27
27
|
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Options for the transform function
|
|
30
|
+
* Options for the transformKey function. The transform function takes them as well.
|
|
31
31
|
*/
|
|
32
|
-
interface
|
|
32
|
+
interface TransformKeyOptions {
|
|
33
33
|
/**
|
|
34
34
|
* The target casing format to transform to
|
|
35
35
|
* @default 'camel'
|
|
36
36
|
*/
|
|
37
37
|
targetCase?: StringCase | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for the transform function
|
|
41
|
+
*/
|
|
42
|
+
interface TransformOptions extends TransformKeyOptions {
|
|
38
43
|
/**
|
|
39
44
|
* How many nesting levels to transform: `1` transforms only the top-level keys, `2`
|
|
40
45
|
* also the keys one level down, and so on. The values of objects, Maps and Sets are one
|
|
@@ -62,6 +67,23 @@ interface TransformOptions {
|
|
|
62
67
|
* @default []
|
|
63
68
|
*/
|
|
64
69
|
exclude?: readonly string[] | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Whether an object reached through several references gives one result, as in the
|
|
72
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
73
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
74
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
75
|
+
* with many objects and arrays up to about twice as fast.
|
|
76
|
+
*
|
|
77
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
78
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
79
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
80
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
81
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
82
|
+
* limit before that, and are copied once per path down to it.
|
|
83
|
+
*
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
keepReferences?: boolean | undefined;
|
|
65
87
|
/**
|
|
66
88
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
67
89
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -69,13 +91,24 @@ interface TransformOptions {
|
|
|
69
91
|
sourceCase?: StringCase | undefined;
|
|
70
92
|
}
|
|
71
93
|
/**
|
|
72
|
-
* Rejects option names
|
|
73
|
-
* whole, so without this an object literal would
|
|
74
|
-
* as `depht` included. The error names the
|
|
94
|
+
* Rejects option names that are not in `Known` (by default, the options of `transform`).
|
|
95
|
+
* The options object is inferred as a whole, so without this an object literal would
|
|
96
|
+
* accept any extra property, typos such as `depht` included. The error names the
|
|
97
|
+
* unknown option.
|
|
75
98
|
*/
|
|
76
|
-
type NoUnknownOptions<Options> = {
|
|
77
|
-
[K in Exclude<keyof Options, keyof
|
|
99
|
+
type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
100
|
+
[K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
|
|
78
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
|
|
104
|
+
* The options parameter is then `TransformKeyOptions` itself rather than an
|
|
105
|
+
* intersection, so TypeScript checks the argument for a property in common with it, as
|
|
106
|
+
* it does for every type whose properties are all optional: `'snake'`, an array, or a
|
|
107
|
+
* stored options object with only a misspelled option is rejected.
|
|
108
|
+
*/
|
|
109
|
+
type NoUnknownKeyOptions<Options> = [
|
|
110
|
+
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
111
|
+
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
79
112
|
/** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
|
|
80
113
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
81
114
|
/**
|
|
@@ -160,7 +193,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
160
193
|
* (tail-recursive, so long keys compile).
|
|
161
194
|
*/
|
|
162
195
|
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 (`
|
|
196
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
164
197
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
165
198
|
/**
|
|
166
199
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -169,14 +202,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
169
202
|
*/
|
|
170
203
|
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;
|
|
171
204
|
/**
|
|
172
|
-
* Keys the runtime leaves unchanged (`
|
|
205
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
173
206
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
174
207
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
175
208
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
176
209
|
*/
|
|
177
210
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
178
211
|
/**
|
|
179
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
212
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
180
213
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
181
214
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
182
215
|
*/
|
|
@@ -200,21 +233,44 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
200
233
|
* (`XAxis` -> `xAxis`)
|
|
201
234
|
*/
|
|
202
235
|
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 (`
|
|
236
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
204
237
|
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` (`
|
|
238
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
206
239
|
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 (`
|
|
240
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
208
241
|
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 (`
|
|
242
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
210
243
|
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
244
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
212
245
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
213
246
|
/**
|
|
214
|
-
*
|
|
247
|
+
* One key `K` (not a union) transformed to one plain target case `To`. A `string` or
|
|
215
248
|
* template-literal key cannot be known exactly, so it becomes `string`.
|
|
216
249
|
*/
|
|
217
|
-
type
|
|
250
|
+
type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
|
|
251
|
+
/**
|
|
252
|
+
* One key `K` transformed to each member of a union of plain target cases `To`.
|
|
253
|
+
*
|
|
254
|
+
* Use `TransformKey` instead. This type is exported only so that declaration files can
|
|
255
|
+
* refer to it: for a fixed key and a generic target case, as in
|
|
256
|
+
* `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
|
|
257
|
+
* the key type is written in terms of it. Without a name to refer to, the compiler
|
|
258
|
+
* writes out its recursive steps, and gets them wrong.
|
|
259
|
+
*/
|
|
260
|
+
type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
|
|
261
|
+
/**
|
|
262
|
+
* The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
|
|
263
|
+
* `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
|
|
264
|
+
* so it becomes `string`. A union of keys or of target cases gives the union of their
|
|
265
|
+
* results, and a string enum member or branded literal stands for its case.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
|
|
270
|
+
* // => 'created_at' | 'user_id'
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
|
|
218
274
|
|
|
219
275
|
/** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
|
|
220
276
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
@@ -403,6 +459,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
|
|
|
403
459
|
* result: `unknown`.
|
|
404
460
|
*/
|
|
405
461
|
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;
|
|
462
|
+
/**
|
|
463
|
+
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
464
|
+
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
465
|
+
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
466
|
+
* `targetCase` typed `any`, give the key in any of the four cases.
|
|
467
|
+
*
|
|
468
|
+
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
469
|
+
* instead, because options with a generic type do not match the overload that returns
|
|
470
|
+
* it.
|
|
471
|
+
*/
|
|
472
|
+
type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
|
|
406
473
|
|
|
407
474
|
/**
|
|
408
475
|
* Transforms the keys of `data` to camelCase, at every nesting level.
|
|
@@ -451,7 +518,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
451
518
|
*
|
|
452
519
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
453
520
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
454
|
-
* accept `depth` or `
|
|
521
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
455
522
|
*/
|
|
456
523
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
457
524
|
sourceCase: From;
|
|
@@ -467,5 +534,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
|
|
|
467
534
|
*/
|
|
468
535
|
declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
|
|
469
536
|
|
|
470
|
-
|
|
471
|
-
|
|
537
|
+
/**
|
|
538
|
+
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
539
|
+
* that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
|
|
540
|
+
* UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
|
|
541
|
+
*
|
|
542
|
+
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
543
|
+
*/
|
|
544
|
+
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
545
|
+
/**
|
|
546
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
547
|
+
* every key. A string that is not a key in one of the four cases, such as
|
|
548
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
549
|
+
* Rules" in the readme).
|
|
550
|
+
*
|
|
551
|
+
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
552
|
+
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
553
|
+
* and a `string` key gives `string`.
|
|
554
|
+
*
|
|
555
|
+
* `Options` captures the options object as a whole, as `transform` does, so each member
|
|
556
|
+
* of a union of options objects gets its own result, and an optional `targetCase` also
|
|
557
|
+
* gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
|
|
558
|
+
* `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
|
|
559
|
+
* value with no property in common with it, such as `'snake'`, is rejected as well. An
|
|
560
|
+
* options object that may itself be `undefined` does not match this overload, and gets
|
|
561
|
+
* a `string` result type from the last one.
|
|
562
|
+
*/
|
|
563
|
+
declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
|
|
564
|
+
/**
|
|
565
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
566
|
+
* every key, typed as a `string`: for an options object that may be `undefined`, or one
|
|
567
|
+
* with properties other than `targetCase`.
|
|
568
|
+
*/
|
|
569
|
+
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
570
|
+
|
|
571
|
+
export { transform, transformKey };
|
|
572
|
+
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.d.mts
CHANGED
|
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
|
27
27
|
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Options for the transform function
|
|
30
|
+
* Options for the transformKey function. The transform function takes them as well.
|
|
31
31
|
*/
|
|
32
|
-
interface
|
|
32
|
+
interface TransformKeyOptions {
|
|
33
33
|
/**
|
|
34
34
|
* The target casing format to transform to
|
|
35
35
|
* @default 'camel'
|
|
36
36
|
*/
|
|
37
37
|
targetCase?: StringCase | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for the transform function
|
|
41
|
+
*/
|
|
42
|
+
interface TransformOptions extends TransformKeyOptions {
|
|
38
43
|
/**
|
|
39
44
|
* How many nesting levels to transform: `1` transforms only the top-level keys, `2`
|
|
40
45
|
* also the keys one level down, and so on. The values of objects, Maps and Sets are one
|
|
@@ -62,6 +67,23 @@ interface TransformOptions {
|
|
|
62
67
|
* @default []
|
|
63
68
|
*/
|
|
64
69
|
exclude?: readonly string[] | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Whether an object reached through several references gives one result, as in the
|
|
72
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
73
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
74
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
75
|
+
* with many objects and arrays up to about twice as fast.
|
|
76
|
+
*
|
|
77
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
78
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
79
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
80
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
81
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
82
|
+
* limit before that, and are copied once per path down to it.
|
|
83
|
+
*
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
keepReferences?: boolean | undefined;
|
|
65
87
|
/**
|
|
66
88
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
67
89
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -69,13 +91,24 @@ interface TransformOptions {
|
|
|
69
91
|
sourceCase?: StringCase | undefined;
|
|
70
92
|
}
|
|
71
93
|
/**
|
|
72
|
-
* Rejects option names
|
|
73
|
-
* whole, so without this an object literal would
|
|
74
|
-
* as `depht` included. The error names the
|
|
94
|
+
* Rejects option names that are not in `Known` (by default, the options of `transform`).
|
|
95
|
+
* The options object is inferred as a whole, so without this an object literal would
|
|
96
|
+
* accept any extra property, typos such as `depht` included. The error names the
|
|
97
|
+
* unknown option.
|
|
75
98
|
*/
|
|
76
|
-
type NoUnknownOptions<Options> = {
|
|
77
|
-
[K in Exclude<keyof Options, keyof
|
|
99
|
+
type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
100
|
+
[K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
|
|
78
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
|
|
104
|
+
* The options parameter is then `TransformKeyOptions` itself rather than an
|
|
105
|
+
* intersection, so TypeScript checks the argument for a property in common with it, as
|
|
106
|
+
* it does for every type whose properties are all optional: `'snake'`, an array, or a
|
|
107
|
+
* stored options object with only a misspelled option is rejected.
|
|
108
|
+
*/
|
|
109
|
+
type NoUnknownKeyOptions<Options> = [
|
|
110
|
+
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
111
|
+
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
79
112
|
/** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
|
|
80
113
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
81
114
|
/**
|
|
@@ -160,7 +193,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
160
193
|
* (tail-recursive, so long keys compile).
|
|
161
194
|
*/
|
|
162
195
|
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 (`
|
|
196
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
164
197
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
165
198
|
/**
|
|
166
199
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -169,14 +202,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
169
202
|
*/
|
|
170
203
|
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;
|
|
171
204
|
/**
|
|
172
|
-
* Keys the runtime leaves unchanged (`
|
|
205
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
173
206
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
174
207
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
175
208
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
176
209
|
*/
|
|
177
210
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
178
211
|
/**
|
|
179
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
212
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
180
213
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
181
214
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
182
215
|
*/
|
|
@@ -200,21 +233,44 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
200
233
|
* (`XAxis` -> `xAxis`)
|
|
201
234
|
*/
|
|
202
235
|
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 (`
|
|
236
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
204
237
|
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` (`
|
|
238
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
206
239
|
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 (`
|
|
240
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
208
241
|
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 (`
|
|
242
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
210
243
|
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
244
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
212
245
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
213
246
|
/**
|
|
214
|
-
*
|
|
247
|
+
* One key `K` (not a union) transformed to one plain target case `To`. A `string` or
|
|
215
248
|
* template-literal key cannot be known exactly, so it becomes `string`.
|
|
216
249
|
*/
|
|
217
|
-
type
|
|
250
|
+
type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
|
|
251
|
+
/**
|
|
252
|
+
* One key `K` transformed to each member of a union of plain target cases `To`.
|
|
253
|
+
*
|
|
254
|
+
* Use `TransformKey` instead. This type is exported only so that declaration files can
|
|
255
|
+
* refer to it: for a fixed key and a generic target case, as in
|
|
256
|
+
* `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
|
|
257
|
+
* the key type is written in terms of it. Without a name to refer to, the compiler
|
|
258
|
+
* writes out its recursive steps, and gets them wrong.
|
|
259
|
+
*/
|
|
260
|
+
type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
|
|
261
|
+
/**
|
|
262
|
+
* The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
|
|
263
|
+
* `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
|
|
264
|
+
* so it becomes `string`. A union of keys or of target cases gives the union of their
|
|
265
|
+
* results, and a string enum member or branded literal stands for its case.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
|
|
270
|
+
* // => 'created_at' | 'user_id'
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
|
|
218
274
|
|
|
219
275
|
/** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
|
|
220
276
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
@@ -403,6 +459,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
|
|
|
403
459
|
* result: `unknown`.
|
|
404
460
|
*/
|
|
405
461
|
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;
|
|
462
|
+
/**
|
|
463
|
+
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
464
|
+
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
465
|
+
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
466
|
+
* `targetCase` typed `any`, give the key in any of the four cases.
|
|
467
|
+
*
|
|
468
|
+
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
469
|
+
* instead, because options with a generic type do not match the overload that returns
|
|
470
|
+
* it.
|
|
471
|
+
*/
|
|
472
|
+
type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
|
|
406
473
|
|
|
407
474
|
/**
|
|
408
475
|
* Transforms the keys of `data` to camelCase, at every nesting level.
|
|
@@ -451,7 +518,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
451
518
|
*
|
|
452
519
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
453
520
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
454
|
-
* accept `depth` or `
|
|
521
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
455
522
|
*/
|
|
456
523
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
457
524
|
sourceCase: From;
|
|
@@ -467,5 +534,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
|
|
|
467
534
|
*/
|
|
468
535
|
declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
|
|
469
536
|
|
|
470
|
-
|
|
471
|
-
|
|
537
|
+
/**
|
|
538
|
+
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
539
|
+
* that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
|
|
540
|
+
* UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
|
|
541
|
+
*
|
|
542
|
+
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
543
|
+
*/
|
|
544
|
+
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
545
|
+
/**
|
|
546
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
547
|
+
* every key. A string that is not a key in one of the four cases, such as
|
|
548
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
549
|
+
* Rules" in the readme).
|
|
550
|
+
*
|
|
551
|
+
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
552
|
+
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
553
|
+
* and a `string` key gives `string`.
|
|
554
|
+
*
|
|
555
|
+
* `Options` captures the options object as a whole, as `transform` does, so each member
|
|
556
|
+
* of a union of options objects gets its own result, and an optional `targetCase` also
|
|
557
|
+
* gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
|
|
558
|
+
* `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
|
|
559
|
+
* value with no property in common with it, such as `'snake'`, is rejected as well. An
|
|
560
|
+
* options object that may itself be `undefined` does not match this overload, and gets
|
|
561
|
+
* a `string` result type from the last one.
|
|
562
|
+
*/
|
|
563
|
+
declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
|
|
564
|
+
/**
|
|
565
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
566
|
+
* every key, typed as a `string`: for an options object that may be `undefined`, or one
|
|
567
|
+
* with properties other than `targetCase`.
|
|
568
|
+
*/
|
|
569
|
+
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
570
|
+
|
|
571
|
+
export { transform, transformKey };
|
|
572
|
+
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
|
27
27
|
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Options for the transform function
|
|
30
|
+
* Options for the transformKey function. The transform function takes them as well.
|
|
31
31
|
*/
|
|
32
|
-
interface
|
|
32
|
+
interface TransformKeyOptions {
|
|
33
33
|
/**
|
|
34
34
|
* The target casing format to transform to
|
|
35
35
|
* @default 'camel'
|
|
36
36
|
*/
|
|
37
37
|
targetCase?: StringCase | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for the transform function
|
|
41
|
+
*/
|
|
42
|
+
interface TransformOptions extends TransformKeyOptions {
|
|
38
43
|
/**
|
|
39
44
|
* How many nesting levels to transform: `1` transforms only the top-level keys, `2`
|
|
40
45
|
* also the keys one level down, and so on. The values of objects, Maps and Sets are one
|
|
@@ -62,6 +67,23 @@ interface TransformOptions {
|
|
|
62
67
|
* @default []
|
|
63
68
|
*/
|
|
64
69
|
exclude?: readonly string[] | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Whether an object reached through several references gives one result, as in the
|
|
72
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
73
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
74
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
75
|
+
* with many objects and arrays up to about twice as fast.
|
|
76
|
+
*
|
|
77
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
78
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
79
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
80
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
81
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
82
|
+
* limit before that, and are copied once per path down to it.
|
|
83
|
+
*
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
keepReferences?: boolean | undefined;
|
|
65
87
|
/**
|
|
66
88
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
67
89
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -69,13 +91,24 @@ interface TransformOptions {
|
|
|
69
91
|
sourceCase?: StringCase | undefined;
|
|
70
92
|
}
|
|
71
93
|
/**
|
|
72
|
-
* Rejects option names
|
|
73
|
-
* whole, so without this an object literal would
|
|
74
|
-
* as `depht` included. The error names the
|
|
94
|
+
* Rejects option names that are not in `Known` (by default, the options of `transform`).
|
|
95
|
+
* The options object is inferred as a whole, so without this an object literal would
|
|
96
|
+
* accept any extra property, typos such as `depht` included. The error names the
|
|
97
|
+
* unknown option.
|
|
75
98
|
*/
|
|
76
|
-
type NoUnknownOptions<Options> = {
|
|
77
|
-
[K in Exclude<keyof Options, keyof
|
|
99
|
+
type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
100
|
+
[K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
|
|
78
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
|
|
104
|
+
* The options parameter is then `TransformKeyOptions` itself rather than an
|
|
105
|
+
* intersection, so TypeScript checks the argument for a property in common with it, as
|
|
106
|
+
* it does for every type whose properties are all optional: `'snake'`, an array, or a
|
|
107
|
+
* stored options object with only a misspelled option is rejected.
|
|
108
|
+
*/
|
|
109
|
+
type NoUnknownKeyOptions<Options> = [
|
|
110
|
+
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
111
|
+
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
79
112
|
/** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
|
|
80
113
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
81
114
|
/**
|
|
@@ -160,7 +193,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
160
193
|
* (tail-recursive, so long keys compile).
|
|
161
194
|
*/
|
|
162
195
|
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 (`
|
|
196
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
164
197
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
165
198
|
/**
|
|
166
199
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -169,14 +202,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
169
202
|
*/
|
|
170
203
|
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;
|
|
171
204
|
/**
|
|
172
|
-
* Keys the runtime leaves unchanged (`
|
|
205
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
173
206
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
174
207
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
175
208
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
176
209
|
*/
|
|
177
210
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
178
211
|
/**
|
|
179
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
212
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
180
213
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
181
214
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
182
215
|
*/
|
|
@@ -200,21 +233,44 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
200
233
|
* (`XAxis` -> `xAxis`)
|
|
201
234
|
*/
|
|
202
235
|
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 (`
|
|
236
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
204
237
|
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` (`
|
|
238
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
206
239
|
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 (`
|
|
240
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
208
241
|
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 (`
|
|
242
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
210
243
|
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
244
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
212
245
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
213
246
|
/**
|
|
214
|
-
*
|
|
247
|
+
* One key `K` (not a union) transformed to one plain target case `To`. A `string` or
|
|
215
248
|
* template-literal key cannot be known exactly, so it becomes `string`.
|
|
216
249
|
*/
|
|
217
|
-
type
|
|
250
|
+
type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
|
|
251
|
+
/**
|
|
252
|
+
* One key `K` transformed to each member of a union of plain target cases `To`.
|
|
253
|
+
*
|
|
254
|
+
* Use `TransformKey` instead. This type is exported only so that declaration files can
|
|
255
|
+
* refer to it: for a fixed key and a generic target case, as in
|
|
256
|
+
* `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
|
|
257
|
+
* the key type is written in terms of it. Without a name to refer to, the compiler
|
|
258
|
+
* writes out its recursive steps, and gets them wrong.
|
|
259
|
+
*/
|
|
260
|
+
type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
|
|
261
|
+
/**
|
|
262
|
+
* The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
|
|
263
|
+
* `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
|
|
264
|
+
* so it becomes `string`. A union of keys or of target cases gives the union of their
|
|
265
|
+
* results, and a string enum member or branded literal stands for its case.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
|
|
270
|
+
* // => 'created_at' | 'user_id'
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
|
|
218
274
|
|
|
219
275
|
/** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
|
|
220
276
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
@@ -403,6 +459,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
|
|
|
403
459
|
* result: `unknown`.
|
|
404
460
|
*/
|
|
405
461
|
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;
|
|
462
|
+
/**
|
|
463
|
+
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
464
|
+
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
465
|
+
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
466
|
+
* `targetCase` typed `any`, give the key in any of the four cases.
|
|
467
|
+
*
|
|
468
|
+
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
469
|
+
* instead, because options with a generic type do not match the overload that returns
|
|
470
|
+
* it.
|
|
471
|
+
*/
|
|
472
|
+
type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
|
|
406
473
|
|
|
407
474
|
/**
|
|
408
475
|
* Transforms the keys of `data` to camelCase, at every nesting level.
|
|
@@ -451,7 +518,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
451
518
|
*
|
|
452
519
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
453
520
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
454
|
-
* accept `depth` or `
|
|
521
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
455
522
|
*/
|
|
456
523
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
457
524
|
sourceCase: From;
|
|
@@ -467,5 +534,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
|
|
|
467
534
|
*/
|
|
468
535
|
declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
|
|
469
536
|
|
|
470
|
-
|
|
471
|
-
|
|
537
|
+
/**
|
|
538
|
+
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
539
|
+
* that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
|
|
540
|
+
* UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
|
|
541
|
+
*
|
|
542
|
+
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
543
|
+
*/
|
|
544
|
+
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
545
|
+
/**
|
|
546
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
547
|
+
* every key. A string that is not a key in one of the four cases, such as
|
|
548
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
549
|
+
* Rules" in the readme).
|
|
550
|
+
*
|
|
551
|
+
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
552
|
+
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
553
|
+
* and a `string` key gives `string`.
|
|
554
|
+
*
|
|
555
|
+
* `Options` captures the options object as a whole, as `transform` does, so each member
|
|
556
|
+
* of a union of options objects gets its own result, and an optional `targetCase` also
|
|
557
|
+
* gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
|
|
558
|
+
* `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
|
|
559
|
+
* value with no property in common with it, such as `'snake'`, is rejected as well. An
|
|
560
|
+
* options object that may itself be `undefined` does not match this overload, and gets
|
|
561
|
+
* a `string` result type from the last one.
|
|
562
|
+
*/
|
|
563
|
+
declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
|
|
564
|
+
/**
|
|
565
|
+
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
566
|
+
* every key, typed as a `string`: for an options object that may be `undefined`, or one
|
|
567
|
+
* with properties other than `targetCase`.
|
|
568
|
+
*/
|
|
569
|
+
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
570
|
+
|
|
571
|
+
export { transform, transformKey };
|
|
572
|
+
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const e=/^[
|
|
1
|
+
"use strict";const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:i,depth:u,keepReferences:f}=t.plain.options,a={targetCase:i,depth:u,keepReferences:f,exclude:null},l={options:a,visitedRefs:[],named:c,other:o(r),root:t};return a.exclude=l,l}function i(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function u(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:function(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const f="[object Object]";function a(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const l={};let p=!1;function d(e,t,n,r){let o=e[t];o.size>=16e6&&(p=!0,o=e[t]=new Map([[l,o]])),o.set(n,r)}function h(e){return e>=48&&e<=57}function y(e){return e>=97&&e<=122}function g(e){return e>=65&&e<=90}const w="s".charCodeAt(0);function v(e,t){if(!g(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(y(n)||h(n))return!0;const r=e.charCodeAt(t+1);return!(!g(n)||!y(r))&&(r!==w||y(e.charCodeAt(t+2)))}function b(e,t,n){return e.split(t).join(n)}function C(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function m(e,t){let n="",r=0;for(let o=1;o<e.length;o++)v(e,o)&&(n+=e.slice(r,o)+t,r=o);return(n+e.slice(r)).toLowerCase()}function k(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=g(r);if(!o&&!y(r))return e;let s=o?1:0,c=!o,i=0,u=0,f=!1,a=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),y(r))c=!0;else if(g(r))s++;else if(95===r)i++,95===a&&(f=!0);else if(45===r)u++,45===a&&(f=!0);else if(!h(r))return e;a=r}if(95===a||45===a)return e;if(0===i&&0===u){if(0===s||1===s&&o)return function(e,t){const n=g(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return m(e,"_");case"kebab":return m(e,"-");case"pascal":return e[0].toUpperCase()+e.slice(1);default:return e}if(!c)return e;switch(t){case"snake":return m(e,"_");case"kebab":return m(e,"-");case"camel":return function(e){let t=1;for(;t<e.length&&!v(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}(e);default:return e}}if(s>0||f||i>0&&u>0)return e;if(i>0)switch(t){case"camel":return C(e,"_",!1);case"pascal":return C(e,"_",!0);case"kebab":return b(e,"_","-");default:return e}if(function(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(h(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}(e))return e;switch(t){case"camel":return C(e,"-",!1);case"pascal":return C(e,"-",!0);case"snake":return b(e,"-","_");default:return e}}const j=new Map;let U,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return k(e,t);if(t!==U){let e=j.get(t);e||(e={current:new Map,previous:new Map},j.set(t,e)),U=t,x=e}const n=x;let r=n.current.get(e);if(void 0!==r)return M=0,r;if(r=n.previous.get(e),void 0===r){if(r=k(e,t),++M>256&&M%8!=0)return r}else M=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const A={reason:"nesting limit reached"};function R(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,h=t.keepReferences;if(h){const t=function(e,t){const n=e.get(t);if(void 0!==n||!p)return n;let r=e.get(l);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(l)}}(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw A;if(Array.isArray(e)){const s=[];return h&&d(n,c,e,s),function(e,t,n,r,o,s){for(const c of e)t.push(null!==c&&"object"==typeof c?R(c,n,r,o,s+1):c)}(e,s,t,n,r,o),s}const y=r+1,g=function(e){try{return Object.prototype.toString.call(e)}catch(e){return f}}(e);if(function(e,t){if(!(e instanceof Map)&&"[object Map]"!==t)return!1;try{return Map.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Map;return h&&d(n,c,e,r),null===t.exclude?function(e,t,n,r,o,s){for(const[c,i]of e)t.set("string"==typeof c?k(c,n.targetCase):c,R(i,n,r,o,s+1))}(e,r,t,n,y,o):function(e,t,n,r,o){for(const[s,c]of e){const e=i(n,s),f=u(n,e);t.set("string"!=typeof s||e.keepKey?s:k(s,n.options.targetCase),R(c,f.options,f.visitedRefs,r,o+1))}}(e,r,t.exclude,y,o),r}if(function(e,t){if(!(e instanceof Set)&&"[object Set]"!==t)return!1;try{return Set.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Set;return h&&d(n,c,e,r),function(e,t,n,r,o,s){for(const c of e)t.add(R(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===f){const r=e,s={};if(h&&d(n,c,e,s),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&a(s,O(e,t.targetCase),R(r[e],t,n,y,o+1));else!function(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=i(n,s),f=u(n,c);a(t,c.keepKey?s:O(s,n.options.targetCase),R(e[s],f.options,f.visitedRefs,r,o+1))}}(r,s,t.exclude,y,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}const S=["camel","snake","pascal","kebab"];function $(e){const n=e||"camel";if(r=n,!S.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${S.join(", ")}.`);var r;return n}function _(n,r,o){const i=void 0===o?null:function(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}(o,r);return null===i?R(n,r):R(n,i.options,i.visitedRefs)}exports.transform=function(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,i=$(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const u=null==n?void 0:n.exclude,f=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof f)throw new TypeError(`Unsupported keepReferences ${t(f)}. Use true or false.`);if(!f)try{return _(e,{targetCase:i,depth:c,keepReferences:f,exclude:null},u)}catch(e){if(e!==A)throw e}return _(e,{targetCase:i,depth:c,keepReferences:!0,exclude:null},u)},exports.transformKey=function(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return k(e,$(null==n?void 0:n.targetCase))};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=/^[
|
|
1
|
+
const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:i,depth:u,keepReferences:f}=t.plain.options,a={targetCase:i,depth:u,keepReferences:f,exclude:null},l={options:a,visitedRefs:[],named:c,other:o(r),root:t};return a.exclude=l,l}function i(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function u(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:function(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const f="[object Object]";function a(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const l={};let p=!1;function d(e,t,n,r){let o=e[t];o.size>=16e6&&(p=!0,o=e[t]=new Map([[l,o]])),o.set(n,r)}function h(e){return e>=48&&e<=57}function y(e){return e>=97&&e<=122}function g(e){return e>=65&&e<=90}const w="s".charCodeAt(0);function v(e,t){if(!g(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(y(n)||h(n))return!0;const r=e.charCodeAt(t+1);return!(!g(n)||!y(r))&&(r!==w||y(e.charCodeAt(t+2)))}function b(e,t,n){return e.split(t).join(n)}function C(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function k(e,t){let n="",r=0;for(let o=1;o<e.length;o++)v(e,o)&&(n+=e.slice(r,o)+t,r=o);return(n+e.slice(r)).toLowerCase()}function m(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=g(r);if(!o&&!y(r))return e;let s=o?1:0,c=!o,i=0,u=0,f=!1,a=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),y(r))c=!0;else if(g(r))s++;else if(95===r)i++,95===a&&(f=!0);else if(45===r)u++,45===a&&(f=!0);else if(!h(r))return e;a=r}if(95===a||45===a)return e;if(0===i&&0===u){if(0===s||1===s&&o)return function(e,t){const n=g(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return k(e,"_");case"kebab":return k(e,"-");case"pascal":return e[0].toUpperCase()+e.slice(1);default:return e}if(!c)return e;switch(t){case"snake":return k(e,"_");case"kebab":return k(e,"-");case"camel":return function(e){let t=1;for(;t<e.length&&!v(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}(e);default:return e}}if(s>0||f||i>0&&u>0)return e;if(i>0)switch(t){case"camel":return C(e,"_",!1);case"pascal":return C(e,"_",!0);case"kebab":return b(e,"_","-");default:return e}if(function(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(h(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}(e))return e;switch(t){case"camel":return C(e,"-",!1);case"pascal":return C(e,"-",!0);case"snake":return b(e,"-","_");default:return e}}const j=new Map;let U,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return m(e,t);if(t!==U){let e=j.get(t);e||(e={current:new Map,previous:new Map},j.set(t,e)),U=t,x=e}const n=x;let r=n.current.get(e);if(void 0!==r)return M=0,r;if(r=n.previous.get(e),void 0===r){if(r=m(e,t),++M>256&&M%8!=0)return r}else M=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const A={reason:"nesting limit reached"};function R(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,h=t.keepReferences;if(h){const t=function(e,t){const n=e.get(t);if(void 0!==n||!p)return n;let r=e.get(l);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(l)}}(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw A;if(Array.isArray(e)){const s=[];return h&&d(n,c,e,s),function(e,t,n,r,o,s){for(const c of e)t.push(null!==c&&"object"==typeof c?R(c,n,r,o,s+1):c)}(e,s,t,n,r,o),s}const y=r+1,g=function(e){try{return Object.prototype.toString.call(e)}catch(e){return f}}(e);if(function(e,t){if(!(e instanceof Map)&&"[object Map]"!==t)return!1;try{return Map.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Map;return h&&d(n,c,e,r),null===t.exclude?function(e,t,n,r,o,s){for(const[c,i]of e)t.set("string"==typeof c?m(c,n.targetCase):c,R(i,n,r,o,s+1))}(e,r,t,n,y,o):function(e,t,n,r,o){for(const[s,c]of e){const e=i(n,s),f=u(n,e);t.set("string"!=typeof s||e.keepKey?s:m(s,n.options.targetCase),R(c,f.options,f.visitedRefs,r,o+1))}}(e,r,t.exclude,y,o),r}if(function(e,t){if(!(e instanceof Set)&&"[object Set]"!==t)return!1;try{return Set.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Set;return h&&d(n,c,e,r),function(e,t,n,r,o,s){for(const c of e)t.add(R(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===f){const r=e,s={};if(h&&d(n,c,e,s),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&a(s,O(e,t.targetCase),R(r[e],t,n,y,o+1));else!function(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=i(n,s),f=u(n,c);a(t,c.keepKey?s:O(s,n.options.targetCase),R(e[s],f.options,f.visitedRefs,r,o+1))}}(r,s,t.exclude,y,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}const S=["camel","snake","pascal","kebab"];function $(e){const n=e||"camel";if(r=n,!S.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${S.join(", ")}.`);var r;return n}function _(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,i=$(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const u=null==n?void 0:n.exclude,f=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof f)throw new TypeError(`Unsupported keepReferences ${t(f)}. Use true or false.`);if(!f)try{return E(e,{targetCase:i,depth:c,keepReferences:f,exclude:null},u)}catch(e){if(e!==A)throw e}return E(e,{targetCase:i,depth:c,keepReferences:!0,exclude:null},u)}function E(n,r,o){const i=void 0===o?null:function(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}(o,r);return null===i?R(n,r):R(n,i.options,i.visitedRefs)}function T(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return m(e,$(null==n?void 0:n.targetCase))}export{_ as transform,T as transformKey};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-case-crafter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, deeply (objects, arrays, Map, Set), with exact TypeScript types",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"test-coverage": "npm test -- --coverage",
|
|
62
62
|
"test:types": "tsc --noEmit",
|
|
63
63
|
"tsc": "tsc --noEmit",
|
|
64
|
-
"test:dist-types": "tsc -p dist-type-tests && tsc -p dist-type-tests/tsconfig.declarations.json && tsc -p dist-type-tests/tsconfig.loose.json && tsc -p dist-type-tests/tsconfig.exact.json",
|
|
64
|
+
"test:dist-types": "tsc -p dist-type-tests && tsc -p dist-type-tests/tsconfig.declarations.json && node dist-type-tests/checkDeclarations.mjs && tsc -p dist-type-tests/tsconfig.consumer.json && tsc -p dist-type-tests/tsconfig.loose.json && tsc -p dist-type-tests/tsconfig.exact.json",
|
|
65
65
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src/ --max-warnings=0 --no-cache",
|
|
66
66
|
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx src/ --fix --max-warnings=0 --no-cache",
|
|
67
67
|
"format": "prettier --write src dist-type-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
|
package/readme.md
CHANGED
|
@@ -39,6 +39,9 @@ const body = transform(user, { targetCase: 'snake' });
|
|
|
39
39
|
returned untouched.
|
|
40
40
|
- **Keys that are data keep their names**: UUIDs always, and any other key whose path
|
|
41
41
|
you list in [`exclude`](#leaving-keys-unchanged), such as `'users_by_id.*'`.
|
|
42
|
+
- **Single keys too**: [`transformKey`](#transformkeykey-options) converts one key by
|
|
43
|
+
the same rules, `transformKey('userID', { targetCase: 'snake' })` → `'user_id'`, typed
|
|
44
|
+
`'user_id'`.
|
|
42
45
|
- No dependencies, with ES module and CommonJS builds.
|
|
43
46
|
|
|
44
47
|
## Installation
|
|
@@ -162,6 +165,24 @@ console.log(transform(strings, { targetCase: 'camel' }));
|
|
|
162
165
|
|
|
163
166
|
---
|
|
164
167
|
|
|
168
|
+
### 7. Convert a single key with `transformKey`
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { transformKey } from 'deep-case-crafter';
|
|
172
|
+
|
|
173
|
+
transformKey('user_id'); // 'userId'
|
|
174
|
+
transformKey('userID', { targetCase: 'snake' }); // 'user_id'
|
|
175
|
+
|
|
176
|
+
type SortField = 'createdAt' | 'userID';
|
|
177
|
+
|
|
178
|
+
function toSortParam(field: SortField) {
|
|
179
|
+
return transformKey(field, { targetCase: 'snake' });
|
|
180
|
+
// ^? 'created_at' | 'user_id'
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
165
186
|
## How it compares
|
|
166
187
|
|
|
167
188
|
Checked in October 2026 against
|
|
@@ -189,10 +210,13 @@ Where the others are better or different:
|
|
|
189
210
|
|
|
190
211
|
- **Bundle size**: bundled and minified with esbuild, ts-case-convert's `objectToCamel`
|
|
191
212
|
and `objectToSnake` take about 1.1 KB gzipped, deep-case-crafter's `transform` about
|
|
192
|
-
2.
|
|
213
|
+
2.8 KB.
|
|
193
214
|
- **More output**: ts-case-convert and es-toolkit also convert to SCREAMING_SNAKE_CASE,
|
|
194
|
-
|
|
195
|
-
|
|
215
|
+
which deep-case-crafter does not.
|
|
216
|
+
- **Single strings**: like `transformKey`, ts-case-convert's `toSnake('userId')` and
|
|
217
|
+
es-toolkit's `snakeCase('userId')` return `'user_id'`. es-toolkit's functions also
|
|
218
|
+
convert text, such as `'Hello World'` → `'hello_world'`, which `transformKey` returns
|
|
219
|
+
unchanged.
|
|
196
220
|
- **Digits**: ts-case-convert separates digits by default (`addressLine1` →
|
|
197
221
|
`address_line_1`), so `address_line_1` → `addressLine1` → `address_line_1`
|
|
198
222
|
round-trips. Its `ts-case-convert/no-split-numbers` entry point keeps digits attached
|
|
@@ -274,12 +298,14 @@ The result **types** match all of this, so `result.get_http_response` and
|
|
|
274
298
|
- `targetCase` (optional): `'camel' | 'snake' | 'pascal' | 'kebab'` (default: `'camel'`, also used for any falsy value such as `''`, `null` or `0`). Any other value throws a `TypeError`.
|
|
275
299
|
- `depth` (optional): **How many nesting levels to transform** (default: `Infinity` — the whole structure; `null` means the same). `1` transforms only the top-level keys, `2` the keys one level down as well, and so on. The values of objects, Maps and Sets are one level deeper; array items stay on their array's level. A value that is not a number (`NaN`, `'3'`, `3n`) throws a `TypeError`.
|
|
276
300
|
- `exclude` (optional): **Keys to leave unchanged**, as paths of keys joined with dots, such as `['users_by_id.*', 'metadata.*']` (default: none; `null` means the same). See [Leaving keys unchanged](#leaving-keys-unchanged). Anything but an array of such paths throws a `TypeError`.
|
|
277
|
-
- `
|
|
301
|
+
- `keepReferences` (optional): **Whether shared and circular references keep their shape** (default: `true`; `null` means the same). An object reached through several references then gives one result, and a circular reference points back into the result. Pass `false` for data that cannot contain such references, such as the output of `JSON.parse`, to skip that bookkeeping: inputs with many objects and arrays convert up to about twice as fast. Use it for tree-shaped data only: an object is then copied once for every path that leads to it, so shared objects inside shared objects multiply. Data with more than 256 nested objects, arrays, Maps and Sets on one path, as circular references always have, is converted again from the start with the bookkeeping, so it takes longer and getters can run twice. With a finite `depth`, circular references stop at the depth limit before that, and are copied once per path down to it. Anything but a boolean throws a `TypeError`.
|
|
302
|
+
- `sourceCase` (**deprecated**): has no effect. Every key's case is detected from the key itself, at runtime and in the result type. It is still accepted, and so is the 2.x form with explicit type arguments, `transform<Data, 'snake', 'camel'>(data, { sourceCase: 'snake', targetCase: 'camel' })`, except together with `depth`, `exclude` or `keepReferences` (leave the type arguments out to use them); the key types follow the runtime, not `sourceCase` (see [3.0.0 in the changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md#300---2026-10-25)).
|
|
278
303
|
|
|
279
304
|
Circular references are resolved to the transformed object. With a finite `depth`, a
|
|
280
305
|
cycle is transformed once per level down to the limit instead: the result grows with the
|
|
281
|
-
depth, and a depth of more than about a thousand levels on cyclic data
|
|
282
|
-
stack and
|
|
306
|
+
depth, and a depth of more than about a thousand levels on cyclic data can exceed the
|
|
307
|
+
call stack and throw a `RangeError` (see [Limits](#limits)). Omit `depth` for cyclic
|
|
308
|
+
data, or keep it small.
|
|
283
309
|
|
|
284
310
|
### Leaving keys unchanged
|
|
285
311
|
|
|
@@ -358,6 +384,33 @@ type of the `exclude` paths, such as `['metadata.*']`.
|
|
|
358
384
|
untouched when it has a `Symbol.toStringTag` (as every class implementing `Map` or
|
|
359
385
|
`Set` does), and rebuilt as a plain object otherwise.
|
|
360
386
|
|
|
387
|
+
### `transformKey(key, options)`
|
|
388
|
+
|
|
389
|
+
Converts a single key by the same rules as the keys of `transform`'s result.
|
|
390
|
+
|
|
391
|
+
- **key**: The key to transform, a string. Anything else throws a `TypeError`.
|
|
392
|
+
- **options**:
|
|
393
|
+
- `targetCase` (optional): as for `transform` (default: `'camel'`). Pass it in an
|
|
394
|
+
object: `transformKey(key, 'snake')` throws a `TypeError`.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
transformKey('HTTPResponse', { targetCase: 'snake' }); // 'http_response'
|
|
398
|
+
transformKey('Hello World', { targetCase: 'snake' }); // 'Hello World'
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
It converts names, not text: a string that is not a key in one of the four cases is
|
|
402
|
+
returned unchanged, such as `'Hello World'`, `'USER_ID'`, `'_id'` or a UUID (see
|
|
403
|
+
[Key Preservation Rules](#key-preservation-rules)).
|
|
404
|
+
|
|
405
|
+
The result type is the converted key. A string literal gives a literal, a union of keys
|
|
406
|
+
or of target cases gives the union of their results, and a `string` gives `string`.
|
|
407
|
+
`TransformKey<Key, TargetCase>` names it: `TransformKey<'userID', 'snake'>` is
|
|
408
|
+
`'user_id'`. Options that are not known exactly, such as an options object that may be
|
|
409
|
+
`undefined`, give a wider type: `string`, or the key in any of the four cases.
|
|
410
|
+
|
|
411
|
+
To use it in a callback, wrap it in a function:
|
|
412
|
+
`keys.map((key) => transformKey(key, { targetCase: 'snake' }))`.
|
|
413
|
+
|
|
361
414
|
---
|
|
362
415
|
|
|
363
416
|
## Advanced: explicit output type overload
|
|
@@ -399,9 +452,9 @@ Trailing digits are kept but treated as part of the word, so they do **not** by
|
|
|
399
452
|
themselves preserve a key — `address_1` becomes `address1`, and the single word
|
|
400
453
|
`item2` stays `item2`.
|
|
401
454
|
|
|
402
|
-
These rules apply to `Map` keys and to the result types as well. To
|
|
403
|
-
unchanged, such as IDs or the keys of free-form metadata, list their
|
|
404
|
-
[`exclude`](#leaving-keys-unchanged).
|
|
455
|
+
These rules apply to `Map` keys, to `transformKey` and to the result types as well. To
|
|
456
|
+
keep other keys unchanged, such as IDs or the keys of free-form metadata, list their
|
|
457
|
+
paths in [`exclude`](#leaving-keys-unchanged).
|
|
405
458
|
|
|
406
459
|
---
|
|
407
460
|
|
|
@@ -425,14 +478,36 @@ Worth knowing:
|
|
|
425
478
|
`userId`), the last one wins.
|
|
426
479
|
- Every property is read once, in order, and its value is transformed before the next one
|
|
427
480
|
is read; getters run during the transformation. Maps are read through their iterator.
|
|
481
|
+
With `keepReferences: false`, data with more than 256 nested objects, arrays, Maps and
|
|
482
|
+
Sets on one path is read a second time.
|
|
428
483
|
- With `depth`, values below the limit are returned as they are: the result shares them
|
|
429
484
|
with the input instead of copying them. An object reached at several levels is
|
|
430
485
|
transformed once per level.
|
|
431
486
|
- With `exclude`, an object reached through several keys is transformed once for each
|
|
432
487
|
set of paths that match below them: one result when the same paths continue below
|
|
433
488
|
every key, one per set otherwise.
|
|
434
|
-
- The transformation is recursive
|
|
435
|
-
|
|
489
|
+
- The transformation is recursive, so very deeply nested data hits a limit: see
|
|
490
|
+
[Limits](#limits).
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Limits
|
|
495
|
+
|
|
496
|
+
Measured with Node 24 on an Apple M4 Pro, with Node's default stack and memory sizes:
|
|
497
|
+
|
|
498
|
+
- **Nesting depth.** The conversion is recursive. Depending on the shape, the first call
|
|
499
|
+
in a process converts 1,300 to 2,400 levels of nesting (nested Maps the fewest, nested
|
|
500
|
+
objects the most), and once the code is warm about 2,600 (nested arrays) to 4,400;
|
|
501
|
+
deeper data throws a `RangeError`. API data nests far less, but untrusted input can nest
|
|
502
|
+
deeper (`JSON.parse` accepts millions of levels), so catch the error where such input is
|
|
503
|
+
converted.
|
|
504
|
+
- **Size.** There is no fixed limit on keys, objects or array items. The result is a full
|
|
505
|
+
copy, so the input and the result must both fit in memory: converting 1 million records
|
|
506
|
+
with 30 million keys (725 MB in memory) needed 1.5 GB more at its peak, and 1.1 GB with
|
|
507
|
+
`keepReferences: false`. When memory runs out, Node ends the process, which cannot be
|
|
508
|
+
caught.
|
|
509
|
+
- **Long keys.** Conversion time grows linearly with a key's length: about 20 ms for a key
|
|
510
|
+
of a million characters.
|
|
436
511
|
|
|
437
512
|
---
|
|
438
513
|
|
|
@@ -442,8 +517,8 @@ deep-case-crafter follows [semantic versioning](https://semver.org). The v3 API
|
|
|
442
517
|
stable. Key outputs change only to fix bugs, and every change is listed in the
|
|
443
518
|
[changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md): if you
|
|
444
519
|
store converted keys, such as database columns, check it before upgrading. A minor
|
|
445
|
-
version can add an option
|
|
446
|
-
|
|
520
|
+
version can add an option, which `Required<TransformOptions>` and
|
|
521
|
+
`Required<TransformKeyOptions>` then require.
|
|
447
522
|
|
|
448
523
|
### Upgrading to v3
|
|
449
524
|
|