deep-case-crafter 3.2.0 → 3.4.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 CHANGED
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
15
15
  type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
16
16
 
17
17
  /**
18
- * Represents the supported string case formats for transformation
18
+ * Represents the supported string case formats for transformation. `'constant'` is
19
+ * CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
20
+ * unchanged by every target case.
19
21
  */
20
- type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
22
+ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
21
23
  /**
22
24
  * The plain literal of a target case: a string enum member or a branded literal such as
23
25
  * `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
24
26
  * detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
25
27
  * narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
28
+ *
29
+ * Not meant to be used directly. It is exported so that declaration files of code with a
30
+ * generic target case refer to it instead of writing out the list of target cases, which
31
+ * would leave out cases added later.
26
32
  */
27
- type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
33
+ type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
28
34
 
29
35
  /**
30
36
  * Options for the transformKey function. The transform function takes them as well.
31
37
  */
32
38
  interface TransformKeyOptions {
33
39
  /**
34
- * The target casing format to transform to
40
+ * The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
41
+ * `USER_ID`.
35
42
  * @default 'camel'
36
43
  */
37
44
  targetCase?: StringCase | undefined;
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
67
74
  * @default []
68
75
  */
69
76
  exclude?: readonly string[] | undefined;
77
+ /**
78
+ * Whether an object reached through several references gives one result, as in the
79
+ * input: shared objects stay shared, and circular references point back into the
80
+ * result. Pass `false` for data that cannot contain such references, such as the
81
+ * output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
82
+ * with many objects and arrays up to about twice as fast.
83
+ *
84
+ * Use it only for tree-shaped data. An object is then copied once for every path that
85
+ * leads to it, so shared objects inside shared objects multiply. Data with more than
86
+ * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
+ * have, is converted again from the start with the bookkeeping, so it takes longer and
88
+ * getters can run twice. With a finite `depth`, circular references stop at the depth
89
+ * limit before that, and are copied once per path down to it.
90
+ *
91
+ * @default true
92
+ */
93
+ keepReferences?: boolean | undefined;
70
94
  /**
71
95
  * @deprecated Has no effect. Every key's case is detected from the key itself, both at
72
96
  * runtime and in the result type, so there is nothing to declare.
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
92
116
  type NoUnknownKeyOptions<Options> = [
93
117
  Exclude<keyof Options, keyof TransformKeyOptions>
94
118
  ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
95
- /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
119
+ /**
120
+ * `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
121
+ *
122
+ * Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
123
+ * files of code with a generic target case refer to it by name instead of writing out its
124
+ * definition.
125
+ */
96
126
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
97
127
  /**
98
128
  * The target case the runtime uses for one options object: `'camel'` when `targetCase`
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
176
206
  * (tail-recursive, so long keys compile).
177
207
  */
178
208
  type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
179
- /** The canonical UUID form, where `x` stands for a hexadecimal digit (`UUID_REGEX`) */
209
+ /** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
180
210
  type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
181
211
  /**
182
212
  * `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
185
215
  */
186
216
  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;
187
217
  /**
188
- * Keys the runtime leaves unchanged (`prepareStringTransformation`): keys containing a
218
+ * Keys the runtime leaves unchanged (`transformCase`): keys containing a
189
219
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
190
220
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
191
221
  * keys that the other rules do not preserve are compared with the UUID form.
192
222
  */
193
223
  type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
194
224
  /**
195
- * The case the runtime detects for a key that is not preserved (`detectStringCase`), or
225
+ * The case the runtime detects for a key that is not preserved (`transformCase`), or
196
226
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
197
227
  * rather than `null`, which every type accepts without `strictNullChecks`.
198
228
  */
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
216
246
  * (`XAxis` -> `xAxis`)
217
247
  */
218
248
  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;
219
- /** snake_case/kebab-case to camelCase (`snakeToCamelCase`, `kebabToCamelCase`) */
249
+ /** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
220
250
  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}`;
221
- /** Replaces every `From` separator with `To` (`snakeToKebabCase`, `kebabToSnakeCase`) */
251
+ /** Replaces every `From` separator with `To` (`replaceSeparator`) */
222
252
  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}`;
223
- /** A single word in the target case (`transformSingleWord`) */
224
- type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
225
- /** A key of the detected case `From` in the target case (`formatterMap`) */
226
- 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' ? '_' : '-'>;
253
+ /** A single word in the target case (`singleWordToCase`) */
254
+ type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
255
+ /** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
256
+ 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' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
227
257
  /** Applies the detected case of `K` (`transformCase`) */
228
258
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
229
259
  /**
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
446
476
  * The key type `transformKey` returns for an options object. Each member of a union of
447
477
  * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
478
  * which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
449
- * `targetCase` typed `any`, give the key in any of the four cases.
479
+ * `targetCase` typed `any`, give the key in any of the target cases.
450
480
  *
451
481
  * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
482
  * instead, because options with a generic type do not match the overload that returns
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
501
531
  *
502
532
  * @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
503
533
  * and the result type is inferred, `depth` and `exclude` included. This form does not
504
- * accept `depth` or `exclude`.
534
+ * accept `depth`, `exclude` or `keepReferences`.
505
535
  */
506
536
  declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
507
537
  sourceCase: From;
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
519
549
 
520
550
  /**
521
551
  * Transforms one key to camelCase, the way `transform` transforms every key. A string
522
- * that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
523
- * UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
552
+ * that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
553
+ * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
554
+ * Rules" in the readme).
524
555
  *
525
556
  * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
557
  */
527
558
  declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
559
  /**
529
560
  * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
530
- * every key. A string that is not a key in one of the four cases, such as
531
- * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
532
- * Rules" in the readme).
561
+ * every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
562
+ * such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
563
+ * Preservation Rules" in the readme).
533
564
  *
534
565
  * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
566
  * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
552
583
  declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
584
 
554
585
  export { transform, transformKey };
555
- export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
586
+ export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
package/dist/index.d.mts CHANGED
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
15
15
  type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
16
16
 
17
17
  /**
18
- * Represents the supported string case formats for transformation
18
+ * Represents the supported string case formats for transformation. `'constant'` is
19
+ * CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
20
+ * unchanged by every target case.
19
21
  */
20
- type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
22
+ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
21
23
  /**
22
24
  * The plain literal of a target case: a string enum member or a branded literal such as
23
25
  * `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
24
26
  * detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
25
27
  * narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
28
+ *
29
+ * Not meant to be used directly. It is exported so that declaration files of code with a
30
+ * generic target case refer to it instead of writing out the list of target cases, which
31
+ * would leave out cases added later.
26
32
  */
27
- type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
33
+ type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
28
34
 
29
35
  /**
30
36
  * Options for the transformKey function. The transform function takes them as well.
31
37
  */
32
38
  interface TransformKeyOptions {
33
39
  /**
34
- * The target casing format to transform to
40
+ * The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
41
+ * `USER_ID`.
35
42
  * @default 'camel'
36
43
  */
37
44
  targetCase?: StringCase | undefined;
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
67
74
  * @default []
68
75
  */
69
76
  exclude?: readonly string[] | undefined;
77
+ /**
78
+ * Whether an object reached through several references gives one result, as in the
79
+ * input: shared objects stay shared, and circular references point back into the
80
+ * result. Pass `false` for data that cannot contain such references, such as the
81
+ * output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
82
+ * with many objects and arrays up to about twice as fast.
83
+ *
84
+ * Use it only for tree-shaped data. An object is then copied once for every path that
85
+ * leads to it, so shared objects inside shared objects multiply. Data with more than
86
+ * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
+ * have, is converted again from the start with the bookkeeping, so it takes longer and
88
+ * getters can run twice. With a finite `depth`, circular references stop at the depth
89
+ * limit before that, and are copied once per path down to it.
90
+ *
91
+ * @default true
92
+ */
93
+ keepReferences?: boolean | undefined;
70
94
  /**
71
95
  * @deprecated Has no effect. Every key's case is detected from the key itself, both at
72
96
  * runtime and in the result type, so there is nothing to declare.
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
92
116
  type NoUnknownKeyOptions<Options> = [
93
117
  Exclude<keyof Options, keyof TransformKeyOptions>
94
118
  ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
95
- /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
119
+ /**
120
+ * `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
121
+ *
122
+ * Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
123
+ * files of code with a generic target case refer to it by name instead of writing out its
124
+ * definition.
125
+ */
96
126
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
97
127
  /**
98
128
  * The target case the runtime uses for one options object: `'camel'` when `targetCase`
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
176
206
  * (tail-recursive, so long keys compile).
177
207
  */
178
208
  type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
179
- /** The canonical UUID form, where `x` stands for a hexadecimal digit (`UUID_REGEX`) */
209
+ /** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
180
210
  type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
181
211
  /**
182
212
  * `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
185
215
  */
186
216
  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;
187
217
  /**
188
- * Keys the runtime leaves unchanged (`prepareStringTransformation`): keys containing a
218
+ * Keys the runtime leaves unchanged (`transformCase`): keys containing a
189
219
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
190
220
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
191
221
  * keys that the other rules do not preserve are compared with the UUID form.
192
222
  */
193
223
  type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
194
224
  /**
195
- * The case the runtime detects for a key that is not preserved (`detectStringCase`), or
225
+ * The case the runtime detects for a key that is not preserved (`transformCase`), or
196
226
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
197
227
  * rather than `null`, which every type accepts without `strictNullChecks`.
198
228
  */
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
216
246
  * (`XAxis` -> `xAxis`)
217
247
  */
218
248
  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;
219
- /** snake_case/kebab-case to camelCase (`snakeToCamelCase`, `kebabToCamelCase`) */
249
+ /** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
220
250
  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}`;
221
- /** Replaces every `From` separator with `To` (`snakeToKebabCase`, `kebabToSnakeCase`) */
251
+ /** Replaces every `From` separator with `To` (`replaceSeparator`) */
222
252
  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}`;
223
- /** A single word in the target case (`transformSingleWord`) */
224
- type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
225
- /** A key of the detected case `From` in the target case (`formatterMap`) */
226
- 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' ? '_' : '-'>;
253
+ /** A single word in the target case (`singleWordToCase`) */
254
+ type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
255
+ /** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
256
+ 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' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
227
257
  /** Applies the detected case of `K` (`transformCase`) */
228
258
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
229
259
  /**
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
446
476
  * The key type `transformKey` returns for an options object. Each member of a union of
447
477
  * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
478
  * which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
449
- * `targetCase` typed `any`, give the key in any of the four cases.
479
+ * `targetCase` typed `any`, give the key in any of the target cases.
450
480
  *
451
481
  * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
482
  * instead, because options with a generic type do not match the overload that returns
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
501
531
  *
502
532
  * @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
503
533
  * and the result type is inferred, `depth` and `exclude` included. This form does not
504
- * accept `depth` or `exclude`.
534
+ * accept `depth`, `exclude` or `keepReferences`.
505
535
  */
506
536
  declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
507
537
  sourceCase: From;
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
519
549
 
520
550
  /**
521
551
  * Transforms one key to camelCase, the way `transform` transforms every key. A string
522
- * that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
523
- * UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
552
+ * that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
553
+ * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
554
+ * Rules" in the readme).
524
555
  *
525
556
  * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
557
  */
527
558
  declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
559
  /**
529
560
  * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
530
- * every key. A string that is not a key in one of the four cases, such as
531
- * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
532
- * Rules" in the readme).
561
+ * every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
562
+ * such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
563
+ * Preservation Rules" in the readme).
533
564
  *
534
565
  * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
566
  * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
552
583
  declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
584
 
554
585
  export { transform, transformKey };
555
- export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
586
+ export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
package/dist/index.d.ts CHANGED
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
15
15
  type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
16
16
 
17
17
  /**
18
- * Represents the supported string case formats for transformation
18
+ * Represents the supported string case formats for transformation. `'constant'` is
19
+ * CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
20
+ * unchanged by every target case.
19
21
  */
20
- type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
22
+ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
21
23
  /**
22
24
  * The plain literal of a target case: a string enum member or a branded literal such as
23
25
  * `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
24
26
  * detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
25
27
  * narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
28
+ *
29
+ * Not meant to be used directly. It is exported so that declaration files of code with a
30
+ * generic target case refer to it instead of writing out the list of target cases, which
31
+ * would leave out cases added later.
26
32
  */
27
- type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
33
+ type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
28
34
 
29
35
  /**
30
36
  * Options for the transformKey function. The transform function takes them as well.
31
37
  */
32
38
  interface TransformKeyOptions {
33
39
  /**
34
- * The target casing format to transform to
40
+ * The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
41
+ * `USER_ID`.
35
42
  * @default 'camel'
36
43
  */
37
44
  targetCase?: StringCase | undefined;
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
67
74
  * @default []
68
75
  */
69
76
  exclude?: readonly string[] | undefined;
77
+ /**
78
+ * Whether an object reached through several references gives one result, as in the
79
+ * input: shared objects stay shared, and circular references point back into the
80
+ * result. Pass `false` for data that cannot contain such references, such as the
81
+ * output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
82
+ * with many objects and arrays up to about twice as fast.
83
+ *
84
+ * Use it only for tree-shaped data. An object is then copied once for every path that
85
+ * leads to it, so shared objects inside shared objects multiply. Data with more than
86
+ * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
+ * have, is converted again from the start with the bookkeeping, so it takes longer and
88
+ * getters can run twice. With a finite `depth`, circular references stop at the depth
89
+ * limit before that, and are copied once per path down to it.
90
+ *
91
+ * @default true
92
+ */
93
+ keepReferences?: boolean | undefined;
70
94
  /**
71
95
  * @deprecated Has no effect. Every key's case is detected from the key itself, both at
72
96
  * runtime and in the result type, so there is nothing to declare.
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
92
116
  type NoUnknownKeyOptions<Options> = [
93
117
  Exclude<keyof Options, keyof TransformKeyOptions>
94
118
  ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
95
- /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
119
+ /**
120
+ * `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
121
+ *
122
+ * Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
123
+ * files of code with a generic target case refer to it by name instead of writing out its
124
+ * definition.
125
+ */
96
126
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
97
127
  /**
98
128
  * The target case the runtime uses for one options object: `'camel'` when `targetCase`
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
176
206
  * (tail-recursive, so long keys compile).
177
207
  */
178
208
  type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
179
- /** The canonical UUID form, where `x` stands for a hexadecimal digit (`UUID_REGEX`) */
209
+ /** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
180
210
  type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
181
211
  /**
182
212
  * `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
185
215
  */
186
216
  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;
187
217
  /**
188
- * Keys the runtime leaves unchanged (`prepareStringTransformation`): keys containing a
218
+ * Keys the runtime leaves unchanged (`transformCase`): keys containing a
189
219
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
190
220
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
191
221
  * keys that the other rules do not preserve are compared with the UUID form.
192
222
  */
193
223
  type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
194
224
  /**
195
- * The case the runtime detects for a key that is not preserved (`detectStringCase`), or
225
+ * The case the runtime detects for a key that is not preserved (`transformCase`), or
196
226
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
197
227
  * rather than `null`, which every type accepts without `strictNullChecks`.
198
228
  */
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
216
246
  * (`XAxis` -> `xAxis`)
217
247
  */
218
248
  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;
219
- /** snake_case/kebab-case to camelCase (`snakeToCamelCase`, `kebabToCamelCase`) */
249
+ /** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
220
250
  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}`;
221
- /** Replaces every `From` separator with `To` (`snakeToKebabCase`, `kebabToSnakeCase`) */
251
+ /** Replaces every `From` separator with `To` (`replaceSeparator`) */
222
252
  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}`;
223
- /** A single word in the target case (`transformSingleWord`) */
224
- type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
225
- /** A key of the detected case `From` in the target case (`formatterMap`) */
226
- 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' ? '_' : '-'>;
253
+ /** A single word in the target case (`singleWordToCase`) */
254
+ type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
255
+ /** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
256
+ 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' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
227
257
  /** Applies the detected case of `K` (`transformCase`) */
228
258
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
229
259
  /**
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
446
476
  * The key type `transformKey` returns for an options object. Each member of a union of
447
477
  * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
478
  * which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
449
- * `targetCase` typed `any`, give the key in any of the four cases.
479
+ * `targetCase` typed `any`, give the key in any of the target cases.
450
480
  *
451
481
  * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
482
  * instead, because options with a generic type do not match the overload that returns
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
501
531
  *
502
532
  * @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
503
533
  * and the result type is inferred, `depth` and `exclude` included. This form does not
504
- * accept `depth` or `exclude`.
534
+ * accept `depth`, `exclude` or `keepReferences`.
505
535
  */
506
536
  declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
507
537
  sourceCase: From;
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
519
549
 
520
550
  /**
521
551
  * Transforms one key to camelCase, the way `transform` transforms every key. A string
522
- * that is not a key in one of the four cases, such as `'Hello World'`, `'USER_ID'` or a
523
- * UUID, is returned unchanged (see "Key Preservation Rules" in the readme).
552
+ * that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
553
+ * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
554
+ * Rules" in the readme).
524
555
  *
525
556
  * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
557
  */
527
558
  declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
559
  /**
529
560
  * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
530
- * every key. A string that is not a key in one of the four cases, such as
531
- * `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
532
- * Rules" in the readme).
561
+ * every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
562
+ * such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
563
+ * Preservation Rules" in the readme).
533
564
  *
534
565
  * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
566
  * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
552
583
  declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
584
 
554
585
  export { transform, transformKey };
555
- export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
586
+ export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";const e=/^[a-z0-9_]+$/,t=/__+/,n=/^[a-z][a-zA-Z0-9]*$/,r=/^[A-Z][a-zA-Z0-9]*$/,o=/^[a-z0-9-]+$/,s=/-{2,}/,u=/[A-Z]/,a=/[a-z]/,i=/^[a-z0-9]+$/,c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,l=/^[^.]+(?:\.[^.]+)*$/;function f(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 p(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const d=[];function g(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?h(t):d,scope:void 0}}function h(e){return 1===e.length?e:[...new Set(e)].sort()}function y(e,t){const n=[];let r;for(const t of e){const[e,o]=p(t);if("*"===e)n.push(o);else{null!=r||(r=new Map);const t=r.get(e);t?t.push(o):r.set(e,[o])}}const o=new Map;for(const[e,t]of null!=r?r:[])o.set(e,g(n.length?[...t,...n]:t));const{targetCase:s,depth:u}=t.plain.options,a={targetCase:s,depth:u,exclude:null},i={options:a,visitedRefs:[],named:o,other:g(n),root:t};return a.exclude=i,i}function w(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function b(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=y(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const v="[object Object]";function C(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}function m(e){return e>="a"&&e<="z"}function k(e){return e>="A"&&e<="Z"}function U(e,t){var n,r,o,s;if(!k(null!==(n=e[t])&&void 0!==n?n:""))return!1;const u=null!==(r=e[t-1])&&void 0!==r?r:"";if(m(u)||(a=u)>="0"&&a<="9")return!0;var a;if(!(k(u)&&m(null!==(o=e[t+1])&&void 0!==o?o:"")))return!1;return!("s"===e[t+1]&&!m(null!==(s=e[t+2])&&void 0!==s?s:""))}function z(e,t){if(!e)return e;let n="";for(let r=0;r<e.length;r++)U(e,r)&&(n+=t),n+=e[r].toLowerCase();return n}const $={snake:{camel:function(e){return e.toLowerCase().replace(/_([a-z0-9])/g,(e,t)=>t.toUpperCase())},pascal:function(e){return e.toLowerCase().replace(/_+([a-z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())},kebab:function(e){return e.replace(/_/g,"-")},snake:e=>e},camel:{snake:function(e){return z(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return z(e,"-")},camel:e=>e},pascal:{snake:function(e){return z(e,"_")},camel:function(e){if(!e)return e;let t=1;for(;t<e.length&&!U(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)},kebab:function(e){return z(e,"-")},pascal:e=>e},kebab:{snake:function(e){return e.replace(/-/g,"_")},camel:function(e){return e.toLowerCase().replace(/-+([a-z0-9])/g,(e,t)=>t.toUpperCase())},pascal:function(e){return e.toLowerCase().replace(/-+([a-z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())},kebab:e=>e}};function j(c){return"string"!=typeof c?null:function(e){if(e.includes("_")||e.includes("-")||e.includes(" "))return!1;if(e.length>0&&!/^[A-Za-z0-9]+$/.test(e))return!1;if(e.length<=1)return!0;const t=e[0],n=e.slice(1);return e===e.toLowerCase()||t===t.toUpperCase()&&n===n.toLowerCase()}(c)?"single":function(n){return"string"==typeof n&&e.test(n)&&!t.test(n)&&(n.includes("_")||i.test(n))}(c)?"snake":function(e){return"string"==typeof e&&n.test(e)&&u.test(e)}(c)?"camel":function(e){return"string"==typeof e&&r.test(e)&&a.test(e)}(c)?"pascal":function(e){return"string"==typeof e&&o.test(e)&&!s.test(e)&&(e.includes("-")||i.test(e))}(c)?"kebab":null}function A(e,t){if(!e)return e;const{string:n,shouldTransform:r,sourceCase:o,singleWord:s}=function(e){if("string"!=typeof e)throw new Error("Input must be a string");if(0===e.length)return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};if(/[^A-Za-z0-9_-]/.test(e)||/^[^A-Za-z]/.test(e)||/[^A-Za-z0-9]$/.test(e)||c.test(e))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=j(e);return t?"single"===t?{string:e,shouldTransform:!0,sourceCase:null,singleWord:!0}:{string:e,shouldTransform:!0,sourceCase:t,singleWord:!1}:{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1}}(e);return r?s?function(e,t){switch(t){case"pascal":return e.charAt(0).toUpperCase()+e.slice(1).toLowerCase();case"camel":return e.charAt(0).toLowerCase()+e.slice(1);case"snake":case"kebab":return e.toLowerCase();default:return e}}(n,t):o&&o!==t?$[o][t](n):n:n}const _=new Map;function x(e,t){var n;if(e.length>100)return A(e,t);let r=_.get(t);r||(r={current:new Map,previous:new Map},_.set(t,r));let o=r.current.get(e);return void 0===o&&(o=null!==(n=r.previous.get(e))&&void 0!==n?n:A(e,t),r.current.size>=1e3&&(r.previous=r.current,r.current=new Map),r.current.set(e,o)),o}function O(e,t,n=[],r=0){var o;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const s=t.depth===1/0?0:r,u=null!==(o=n[s])&&void 0!==o?o:n[s]=new WeakMap;if(u.has(e))return u.get(e);if(Array.isArray(e)){const o=[];return u.set(e,o),function(e,t,n,r,o){for(const s of e)t.push(O(s,n,r,o))}(e,o,t,n,r),o}const a=r+1,i=function(e){try{return Object.prototype.toString.call(e)}catch(e){return v}}(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,i)){const r=new Map;return u.set(e,r),null===t.exclude?function(e,t,n,r,o){for(const[s,u]of e)t.set("string"==typeof s?A(s,n.targetCase):s,O(u,n,r,o))}(e,r,t,n,a):function(e,t,n,r){for(const[o,s]of e){const e=w(n,o),u=b(n,e);t.set("string"!=typeof o||e.keepKey?o:A(o,n.options.targetCase),O(s,u.options,u.visitedRefs,r))}}(e,r,t.exclude,a),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,i)){const r=new Set;return u.set(e,r),function(e,t,n,r,o){for(const s of e)t.add(O(s,n,r,o))}(e,r,t,n,a),r}if(i===v){const r=e,o={};if(u.set(e,o),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&C(o,x(e,t.targetCase),O(r[e],t,n,a));else!function(e,t,n,r){for(const o in e)if(Object.prototype.hasOwnProperty.call(e,o)){const s=w(n,o),u=b(n,s);C(t,s.keepKey?o:x(o,n.options.targetCase),O(e[o],u.options,u.visitedRefs,r))}}(r,o,t.exclude,a);for(const e of Object.getOwnPropertySymbols(r))o[e]=r[e];return o}return e}const M=["camel","snake","pascal","kebab"];function T(e){const t=e||"camel";if(n=t,!M.includes(n))throw new TypeError(`Unsupported targetCase ${f(t)}. Use one of: ${M.join(", ")}.`);var n;return t}exports.transform=function(e,t){var n;const r=null==t?void 0:t.targetCase,o=null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0,s=T(r);if("number"!=typeof o||Number.isNaN(o))throw new TypeError(`Unsupported depth ${f(o)}. Use a number, or omit depth to transform every level.`);const u={targetCase:s,depth:o,exclude:null},a=null==t?void 0:t.exclude,i=void 0===a?null:function(e,t){if(null==e)return null;if(!Array.isArray(e))throw new TypeError(`Unsupported exclude ${f(e)}. Use an array of paths, such as ["metadata.*"].`);for(const t of e)if("string"!=typeof t||!l.test(t))throw new TypeError(`Unsupported exclude path ${f(t)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===e.length)return null;const n={plain:{options:t,visitedRefs:[]},states:new Map};return y(h(e),n)}(a,u);return null===i?O(e,u):O(e,i.options,i.visitedRefs)},exports.transformKey=function(e,t){if("string"!=typeof e)throw new TypeError(`Unsupported key ${f(e)}. Use a string.`);if("string"==typeof t)throw new TypeError(`Unsupported options ${f(t)}. Use an options object: { targetCase: ${f(t)} }.`);return A(e,T(null==t?void 0:t.targetCase))};
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:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function i(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 a="[object Object]";function f(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 C(e,t,n){return e.split(t).join(n)}function b(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 U(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)v(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():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,u=0,i=0,a=!1,f=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)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!h(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){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;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return U(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);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);case"constant":return U(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return b(e,"_",!1);case"pascal":return b(e,"_",!0);case"kebab":return C(e,"_","-");case"constant":return e.toUpperCase();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 b(e,"-",!1);case"pascal":return b(e,"-",!0);case"snake":return C(e,"-","_");case"constant":return C(e,"-","_").toUpperCase();default:return e}}const k=new Map;let j,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return m(e,t);if(t!==j){let e=k.get(t);e||(e={current:new Map,previous:new Map},k.set(t,e)),j=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 _(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?_(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 a}}(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,u]of e)t.set("string"==typeof c?m(c,n.targetCase):c,_(u,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=u(n,s),a=i(n,e);t.set("string"!=typeof s||e.keepKey?s:m(s,n.options.targetCase),_(c,a.options,a.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(_(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===a){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)&&f(s,O(e,t.targetCase),_(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=u(n,s),a=i(n,c);f(t,c.keepKey?s:O(s,n.options.targetCase),_(e[s],a.options,a.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 R=["camel","snake","pascal","kebab","constant"];function S(e){const n=e||"camel";if(r=n,!R.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${R.join(", ")}.`);var r;return n}function $(n,r,o){const u=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===u?_(n,r):_(n,u.options,u.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,u=S(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 i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return $(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==A)throw e}return $(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)},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 m(e,S(null==n?void 0:n.targetCase))};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- const e=/^[a-z0-9_]+$/,t=/__+/,n=/^[a-z][a-zA-Z0-9]*$/,r=/^[A-Z][a-zA-Z0-9]*$/,o=/^[a-z0-9-]+$/,s=/-{2,}/,u=/[A-Z]/,a=/[a-z]/,i=/^[a-z0-9]+$/,c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,l=/^[^.]+(?:\.[^.]+)*$/;function f(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 p(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const d=[];function g(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?h(t):d,scope:void 0}}function h(e){return 1===e.length?e:[...new Set(e)].sort()}function y(e,t){const n=[];let r;for(const t of e){const[e,o]=p(t);if("*"===e)n.push(o);else{null!=r||(r=new Map);const t=r.get(e);t?t.push(o):r.set(e,[o])}}const o=new Map;for(const[e,t]of null!=r?r:[])o.set(e,g(n.length?[...t,...n]:t));const{targetCase:s,depth:u}=t.plain.options,a={targetCase:s,depth:u,exclude:null},i={options:a,visitedRefs:[],named:o,other:g(n),root:t};return a.exclude=i,i}function w(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function b(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=y(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const v="[object Object]";function C(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}function m(e){return e>="a"&&e<="z"}function k(e){return e>="A"&&e<="Z"}function U(e,t){var n,r,o,s;if(!k(null!==(n=e[t])&&void 0!==n?n:""))return!1;const u=null!==(r=e[t-1])&&void 0!==r?r:"";if(m(u)||(a=u)>="0"&&a<="9")return!0;var a;if(!(k(u)&&m(null!==(o=e[t+1])&&void 0!==o?o:"")))return!1;return!("s"===e[t+1]&&!m(null!==(s=e[t+2])&&void 0!==s?s:""))}function z(e,t){if(!e)return e;let n="";for(let r=0;r<e.length;r++)U(e,r)&&(n+=t),n+=e[r].toLowerCase();return n}const $={snake:{camel:function(e){return e.toLowerCase().replace(/_([a-z0-9])/g,(e,t)=>t.toUpperCase())},pascal:function(e){return e.toLowerCase().replace(/_+([a-z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())},kebab:function(e){return e.replace(/_/g,"-")},snake:e=>e},camel:{snake:function(e){return z(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return z(e,"-")},camel:e=>e},pascal:{snake:function(e){return z(e,"_")},camel:function(e){if(!e)return e;let t=1;for(;t<e.length&&!U(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)},kebab:function(e){return z(e,"-")},pascal:e=>e},kebab:{snake:function(e){return e.replace(/-/g,"_")},camel:function(e){return e.toLowerCase().replace(/-+([a-z0-9])/g,(e,t)=>t.toUpperCase())},pascal:function(e){return e.toLowerCase().replace(/-+([a-z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())},kebab:e=>e}};function j(c){return"string"!=typeof c?null:function(e){if(e.includes("_")||e.includes("-")||e.includes(" "))return!1;if(e.length>0&&!/^[A-Za-z0-9]+$/.test(e))return!1;if(e.length<=1)return!0;const t=e[0],n=e.slice(1);return e===e.toLowerCase()||t===t.toUpperCase()&&n===n.toLowerCase()}(c)?"single":function(n){return"string"==typeof n&&e.test(n)&&!t.test(n)&&(n.includes("_")||i.test(n))}(c)?"snake":function(e){return"string"==typeof e&&n.test(e)&&u.test(e)}(c)?"camel":function(e){return"string"==typeof e&&r.test(e)&&a.test(e)}(c)?"pascal":function(e){return"string"==typeof e&&o.test(e)&&!s.test(e)&&(e.includes("-")||i.test(e))}(c)?"kebab":null}function A(e,t){if(!e)return e;const{string:n,shouldTransform:r,sourceCase:o,singleWord:s}=function(e){if("string"!=typeof e)throw new Error("Input must be a string");if(0===e.length)return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};if(/[^A-Za-z0-9_-]/.test(e)||/^[^A-Za-z]/.test(e)||/[^A-Za-z0-9]$/.test(e)||c.test(e))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=j(e);return t?"single"===t?{string:e,shouldTransform:!0,sourceCase:null,singleWord:!0}:{string:e,shouldTransform:!0,sourceCase:t,singleWord:!1}:{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1}}(e);return r?s?function(e,t){switch(t){case"pascal":return e.charAt(0).toUpperCase()+e.slice(1).toLowerCase();case"camel":return e.charAt(0).toLowerCase()+e.slice(1);case"snake":case"kebab":return e.toLowerCase();default:return e}}(n,t):o&&o!==t?$[o][t](n):n:n}const _=new Map;function O(e,t){var n;if(e.length>100)return A(e,t);let r=_.get(t);r||(r={current:new Map,previous:new Map},_.set(t,r));let o=r.current.get(e);return void 0===o&&(o=null!==(n=r.previous.get(e))&&void 0!==n?n:A(e,t),r.current.size>=1e3&&(r.previous=r.current,r.current=new Map),r.current.set(e,o)),o}function x(e,t,n=[],r=0){var o;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const s=t.depth===1/0?0:r,u=null!==(o=n[s])&&void 0!==o?o:n[s]=new WeakMap;if(u.has(e))return u.get(e);if(Array.isArray(e)){const o=[];return u.set(e,o),function(e,t,n,r,o){for(const s of e)t.push(x(s,n,r,o))}(e,o,t,n,r),o}const a=r+1,i=function(e){try{return Object.prototype.toString.call(e)}catch(e){return v}}(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,i)){const r=new Map;return u.set(e,r),null===t.exclude?function(e,t,n,r,o){for(const[s,u]of e)t.set("string"==typeof s?A(s,n.targetCase):s,x(u,n,r,o))}(e,r,t,n,a):function(e,t,n,r){for(const[o,s]of e){const e=w(n,o),u=b(n,e);t.set("string"!=typeof o||e.keepKey?o:A(o,n.options.targetCase),x(s,u.options,u.visitedRefs,r))}}(e,r,t.exclude,a),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,i)){const r=new Set;return u.set(e,r),function(e,t,n,r,o){for(const s of e)t.add(x(s,n,r,o))}(e,r,t,n,a),r}if(i===v){const r=e,o={};if(u.set(e,o),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&C(o,O(e,t.targetCase),x(r[e],t,n,a));else!function(e,t,n,r){for(const o in e)if(Object.prototype.hasOwnProperty.call(e,o)){const s=w(n,o),u=b(n,s);C(t,s.keepKey?o:O(o,n.options.targetCase),x(e[o],u.options,u.visitedRefs,r))}}(r,o,t.exclude,a);for(const e of Object.getOwnPropertySymbols(r))o[e]=r[e];return o}return e}const M=["camel","snake","pascal","kebab"];function T(e){const t=e||"camel";if(n=t,!M.includes(n))throw new TypeError(`Unsupported targetCase ${f(t)}. Use one of: ${M.join(", ")}.`);var n;return t}function L(e,t){var n;const r=null==t?void 0:t.targetCase,o=null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0,s=T(r);if("number"!=typeof o||Number.isNaN(o))throw new TypeError(`Unsupported depth ${f(o)}. Use a number, or omit depth to transform every level.`);const u={targetCase:s,depth:o,exclude:null},a=null==t?void 0:t.exclude,i=void 0===a?null:function(e,t){if(null==e)return null;if(!Array.isArray(e))throw new TypeError(`Unsupported exclude ${f(e)}. Use an array of paths, such as ["metadata.*"].`);for(const t of e)if("string"!=typeof t||!l.test(t))throw new TypeError(`Unsupported exclude path ${f(t)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===e.length)return null;const n={plain:{options:t,visitedRefs:[]},states:new Map};return y(h(e),n)}(a,u);return null===i?x(e,u):x(e,i.options,i.visitedRefs)}function S(e,t){if("string"!=typeof e)throw new TypeError(`Unsupported key ${f(e)}. Use a string.`);if("string"==typeof t)throw new TypeError(`Unsupported options ${f(t)}. Use an options object: { targetCase: ${f(t)} }.`);return A(e,T(null==t?void 0:t.targetCase))}export{L as transform,S as transformKey};
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:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function i(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 a="[object Object]";function f(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 C(e,t,n){return e.split(t).join(n)}function b(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 U(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)v(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():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,u=0,i=0,a=!1,f=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)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!h(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){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;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return U(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);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);case"constant":return U(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return b(e,"_",!1);case"pascal":return b(e,"_",!0);case"kebab":return C(e,"_","-");case"constant":return e.toUpperCase();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 b(e,"-",!1);case"pascal":return b(e,"-",!0);case"snake":return C(e,"-","_");case"constant":return C(e,"-","_").toUpperCase();default:return e}}const m=new Map;let j,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return k(e,t);if(t!==j){let e=m.get(t);e||(e={current:new Map,previous:new Map},m.set(t,e)),j=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 _(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?_(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 a}}(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,u]of e)t.set("string"==typeof c?k(c,n.targetCase):c,_(u,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=u(n,s),a=i(n,e);t.set("string"!=typeof s||e.keepKey?s:k(s,n.options.targetCase),_(c,a.options,a.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(_(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===a){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)&&f(s,O(e,t.targetCase),_(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=u(n,s),a=i(n,c);f(t,c.keepKey?s:O(s,n.options.targetCase),_(e[s],a.options,a.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 R=["camel","snake","pascal","kebab","constant"];function S(e){const n=e||"camel";if(r=n,!R.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${R.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,u=S(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 i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return E(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==A)throw e}return E(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)}function E(n,r,o){const u=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===u?_(n,r):_(n,u.options,u.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 k(e,S(null==n?void 0:n.targetCase))}export{$ as transform,T as transformKey};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deep-case-crafter",
3
- "version": "3.2.0",
4
- "description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, deeply (objects, arrays, Map, Set), with exact TypeScript types",
3
+ "version": "3.4.0",
4
+ "description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, or to CONSTANT_CASE, deeply (objects, arrays, Map, Set), with exact TypeScript types",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -36,12 +36,14 @@
36
36
  "snake_case",
37
37
  "kebab-case",
38
38
  "PascalCase",
39
+ "CONSTANT_CASE",
39
40
  "camelcase",
40
41
  "snakecase",
41
42
  "kebabcase",
42
43
  "pascalcase",
43
44
  "camel-case",
44
45
  "snake-case",
46
+ "constant-case",
45
47
  "case-convert",
46
48
  "convert-case",
47
49
  "case-conversion",
package/readme.md CHANGED
@@ -9,7 +9,8 @@
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/gyrgy/deep-case-crafter/blob/main/LICENSE)
10
10
 
11
11
  Converts the keys of nested objects, arrays, Maps and Sets between camelCase,
12
- snake_case, kebab-case and PascalCase, and types the result exactly.
12
+ snake_case, kebab-case and PascalCase, or to CONSTANT_CASE, and types the result
13
+ exactly.
13
14
 
14
15
  ```typescript
15
16
  import { transform } from 'deep-case-crafter';
@@ -34,6 +35,9 @@ const body = transform(user, { targetCase: 'snake' });
34
35
  keys in different cases can be mixed in one input.
35
36
  - **Acronym-aware**: `userID` → `user_id`, `HTTPResponse` → `http_response` or
36
37
  `httpResponse`.
38
+ - **CONSTANT_CASE output**: `targetCase: 'constant'` turns `userID` into `USER_ID` and
39
+ `HTTPResponse` into `HTTP_RESPONSE`. It is an output case only: the other cases leave
40
+ `USER_ID` as it is.
37
41
  - **Deep**: objects, arrays, `Map` and `Set`, down to an optional `depth`, circular
38
42
  references included. `Date`, `RegExp`, typed arrays and other built-in values are
39
43
  returned untouched.
@@ -89,6 +93,10 @@ console.log(pascalCaseResult);
89
93
  const kebabCaseResult = transform(input, { targetCase: 'kebab' });
90
94
  console.log(kebabCaseResult);
91
95
  // Output: { "user-id": 1, "first-name": "John" }
96
+
97
+ const constantCaseResult = transform(input, { targetCase: 'constant' });
98
+ console.log(constantCaseResult);
99
+ // Output: { USER_ID: 1, FIRST_NAME: 'John' }
92
100
  ```
93
101
 
94
102
  ---
@@ -193,26 +201,28 @@ typed alternative), [camelcase-keys](https://www.npmjs.com/package/camelcase-key
193
201
  `toSnakeCaseKeys`). Plain snake_case keys such as `user_id` become the same camelCase
194
202
  keys in all of them. The table shows where they differ:
195
203
 
196
- | | deep-case-crafter | ts-case-convert | camelcase-keys, snakecase-keys | es-toolkit |
197
- | -------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------- |
198
- | `userID` to snake_case | `user_id` | `user_i_d` | `user_id` | `user_id`, typed `user_i_d` |
199
- | `HTTPResponse` to snake_case | `http_response` | `h_t_t_p_response` | `http_response` | `http_response`, typed `h_t_t_p_response` |
200
- | `isAValidUser` to snake_case | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` |
201
- | `addressLine1` to snake_case | `address_line1` | `address_line_1`, or `address_line1` with `no-split-numbers` | `address_line1` | `address_line_1`, typed `address_line1` |
202
- | Objects inside a `Map` or `Set` | keys converted | outside its documented scope | outside their documented scope | left as they are, as documented |
203
- | Result type for `await res.json()`, an `any` input | `any` | a union that includes `Date` and `null`, so `.userId` does not compile | `any` | `any` |
204
- | A `readonly string[]` field in the result type | `readonly string[]` | an object type whose `.map()` does not compile | `readonly string[]` | an object type: `.map()` compiles, `[0]` and `for...of` do not |
205
- | A `[number, number]` field in the result type | `[number, number]` | `number[]` | `[number, number]` | `number[]` |
206
- | Leaving chosen keys unchanged | `exclude` paths, such as `'metadata.*'` | no option | `exclude` keys by name or RegExp; camelcase-keys also `stopPaths` | no option |
207
- | Target cases | camel, snake, kebab, Pascal | camel, snake, Pascal, SCREAMING_SNAKE | camel and Pascal; snake | camel, snake, kebab, Pascal, SCREAMING_SNAKE |
204
+ | | deep-case-crafter | ts-case-convert | camelcase-keys, snakecase-keys | es-toolkit |
205
+ | -------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------- |
206
+ | `userID` to snake_case | `user_id` | `user_i_d` | `user_id` | `user_id`, typed `user_i_d` |
207
+ | `HTTPResponse` to snake_case | `http_response` | `h_t_t_p_response` | `http_response` | `http_response`, typed `h_t_t_p_response` |
208
+ | `isAValidUser` to snake_case | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` |
209
+ | `addressLine1` to snake_case | `address_line1` | `address_line_1`, or `address_line1` with `no-split-numbers` | `address_line1` | `address_line_1`, typed `address_line1` |
210
+ | Objects inside a `Map` or `Set` | keys converted | outside its documented scope | outside their documented scope | left as they are, as documented |
211
+ | Result type for `await res.json()`, an `any` input | `any` | a union that includes `Date` and `null`, so `.userId` does not compile | `any` | `any` |
212
+ | A `readonly string[]` field in the result type | `readonly string[]` | an object type whose `.map()` does not compile | `readonly string[]` | an object type: `.map()` compiles, `[0]` and `for...of` do not |
213
+ | A `[number, number]` field in the result type | `[number, number]` | `number[]` | `[number, number]` | `number[]` |
214
+ | Leaving chosen keys unchanged | `exclude` paths, such as `'metadata.*'` | no option | `exclude` keys by name or RegExp; camelcase-keys also `stopPaths` | no option |
215
+ | Target cases | camel, snake, kebab, Pascal, SCREAMING_SNAKE | camel, snake, Pascal, SCREAMING_SNAKE | camel and Pascal; snake | camel, snake, kebab, Pascal, SCREAMING_SNAKE |
208
216
 
209
217
  Where the others are better or different:
210
218
 
211
219
  - **Bundle size**: bundled and minified with esbuild, ts-case-convert's `objectToCamel`
212
220
  and `objectToSnake` take about 1.1 KB gzipped, deep-case-crafter's `transform` about
213
- 2.6 KB.
214
- - **More output**: ts-case-convert and es-toolkit also convert to SCREAMING_SNAKE_CASE,
215
- which deep-case-crafter does not.
221
+ 2.8 KB.
222
+ - **CONSTANT_CASE input**: camelcase-keys, snakecase-keys and es-toolkit also read
223
+ SCREAMING_SNAKE_CASE keys, so `USER_ID` becomes `userId` or `user_id`.
224
+ deep-case-crafter converts to CONSTANT_CASE, but leaves keys such as `USER_ID` and
225
+ `ID` unchanged (see [Key Preservation Rules](#key-preservation-rules)).
216
226
  - **Single strings**: like `transformKey`, ts-case-convert's `toSnake('userId')` and
217
227
  es-toolkit's `snakeCase('userId')` return `'user_id'`. es-toolkit's functions also
218
228
  convert text, such as `'Hello World'` → `'hello_world'`, which `transformKey` returns
@@ -231,8 +241,8 @@ Where the others are better or different:
231
241
 
232
242
  ## Acronym-aware conversion
233
243
 
234
- When converting **to** `snake_case` or `kebab-case`, runs of capitals (acronyms)
235
- are treated as whole words instead of being split letter-by-letter:
244
+ When converting **to** `snake_case`, `kebab-case` or `CONSTANT_CASE`, runs of capitals
245
+ (acronyms) are treated as whole words instead of being split letter-by-letter:
236
246
 
237
247
  ```typescript
238
248
  const input = { getHTTPResponse: 1, userID: 2, parseHTMLString: 3 };
@@ -276,7 +286,7 @@ console.log(result);
276
286
  ```
277
287
 
278
288
  Words of two or more capitals, such as `ID`, `USD` or `GET`, are left unchanged, like
279
- `SCREAMING_SNAKE` keys (see [Key Preservation Rules](#key-preservation-rules)). A single
289
+ `CONSTANT_CASE` keys (see [Key Preservation Rules](#key-preservation-rules)). A single
280
290
  capital, with or without digits (`S`, `S3`), is converted as a one-letter word.
281
291
 
282
292
  Converting back to `PascalCase` cannot restore a lowercased acronym: `httpResponse`
@@ -295,15 +305,17 @@ The result **types** match all of this, so `result.get_http_response` and
295
305
 
296
306
  - **data**: The data structure to transform (`object`, `array`, `Map`, or `Set`).
297
307
  - **options**:
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`.
308
+ - `targetCase` (optional): `'camel' | 'snake' | 'pascal' | 'kebab' | 'constant'` (default: `'camel'`, also used for any falsy value such as `''`, `null` or `0`). Any other value throws a `TypeError`. `'constant'` is CONSTANT_CASE, also called SCREAMING_SNAKE_CASE: `userID` becomes `USER_ID`, split into words as for snake_case. It is a target case only: keys such as `USER_ID` or `ID` are left unchanged by every target case, so they do not convert back. A single capital, with or without digits, is still converted as a one-letter word: `'constant'` turns `p99` into `P99`, and camel, snake and kebab turn it back into `p99` (see [Key Preservation Rules](#key-preservation-rules)).
299
309
  - `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`.
300
310
  - `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`.
301
- - `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` or `exclude` (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)).
311
+ - `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`.
312
+ - `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)).
302
313
 
303
314
  Circular references are resolved to the transformed object. With a finite `depth`, a
304
315
  cycle is transformed once per level down to the limit instead: the result grows with the
305
- depth, and a depth of more than about a thousand levels on cyclic data exceeds the call
306
- stack and throws a `RangeError`. Omit `depth` for cyclic data, or keep it small.
316
+ depth, and a depth of more than about a thousand levels on cyclic data can exceed the
317
+ call stack and throw a `RangeError` (see [Limits](#limits)). Omit `depth` for cyclic
318
+ data, or keep it small.
307
319
 
308
320
  ### Leaving keys unchanged
309
321
 
@@ -367,11 +379,14 @@ type of the `exclude` paths, such as `['metadata.*']`.
367
379
  (without `sourceCase`), and options passed with only the input type given
368
380
  (`transform<Data>(data, options)`). Leave the type arguments out to get the precise
369
381
  type.
370
- - When a library exports a value or function built on `transform`, annotate it with
371
- `TransformResult<...>`. Otherwise the result type is written out into its declaration
372
- files: recursive types (trees, JSON values) are cut off as `any`, and very large types
373
- or symbol-keyed members fail to compile (TS7056, TS4118). Generic functions around
374
- `transform` are not affected.
382
+ - When a library exports a value or function built on `transform` or `transformKey`,
383
+ annotate its result with `TransformResult<...>` or `TransformKey<...>`. Otherwise the
384
+ result type is written out into its declaration files. For data of a fixed type,
385
+ recursive types (trees, JSON values) are then cut off as `any`, and very large types or
386
+ symbol-keyed members fail to compile (TS7056, TS4118). For a target case typed
387
+ `StringCase`, generic data included, the result can cover only the cases that existed
388
+ when the library was built, not cases added later. A generic target case, as in
389
+ `<C extends StringCase>(targetCase: C)`, is not affected.
375
390
  - Some values cannot be told apart by their types. `Error`, `URL`, boxed primitives,
376
391
  iterators, module namespace objects and other built-ins whose types do not declare a
377
392
  `Symbol.toStringTag` are returned untouched, but typed as plain objects with
@@ -396,15 +411,15 @@ transformKey('HTTPResponse', { targetCase: 'snake' }); // 'http_response'
396
411
  transformKey('Hello World', { targetCase: 'snake' }); // 'Hello World'
397
412
  ```
398
413
 
399
- It converts names, not text: a string that is not a key in one of the four cases is
400
- returned unchanged, such as `'Hello World'`, `'USER_ID'`, `'_id'` or a UUID (see
401
- [Key Preservation Rules](#key-preservation-rules)).
414
+ It converts names, not text: a string that is not a camelCase, PascalCase, snake_case or
415
+ kebab-case key is returned unchanged, such as `'Hello World'`, `'USER_ID'`, `'_id'` or a
416
+ UUID (see [Key Preservation Rules](#key-preservation-rules)).
402
417
 
403
418
  The result type is the converted key. A string literal gives a literal, a union of keys
404
419
  or of target cases gives the union of their results, and a `string` gives `string`.
405
420
  `TransformKey<Key, TargetCase>` names it: `TransformKey<'userID', 'snake'>` is
406
421
  `'user_id'`. Options that are not known exactly, such as an options object that may be
407
- `undefined`, give a wider type: `string`, or the key in any of the four cases.
422
+ `undefined`, give a wider type: `string`, or the key in any of the target cases.
408
423
 
409
424
  To use it in a callback, wrap it in a function:
410
425
  `keys.map((key) => transformKey(key, { targetCase: 'snake' }))`.
@@ -446,6 +461,10 @@ A key is left **completely unchanged** when it:
446
461
  - **is not written in one recognisable case**, e.g. `mixed_Case`, `USER_ID`, `a_b-c`, or
447
462
  is a word of two or more capitals, e.g. `ID`, `USD`, `B2B`.
448
463
 
464
+ CONSTANT_CASE keys such as `USER_ID` fall under the last rule, so every target case keeps
465
+ them: `'constant'` is a target case only. A single capital, with or without digits
466
+ (`X`, `P99`), is not kept: it is converted as a one-letter word.
467
+
449
468
  Trailing digits are kept but treated as part of the word, so they do **not** by
450
469
  themselves preserve a key — `address_1` becomes `address1`, and the single word
451
470
  `item2` stays `item2`.
@@ -476,14 +495,36 @@ Worth knowing:
476
495
  `userId`), the last one wins.
477
496
  - Every property is read once, in order, and its value is transformed before the next one
478
497
  is read; getters run during the transformation. Maps are read through their iterator.
498
+ With `keepReferences: false`, data with more than 256 nested objects, arrays, Maps and
499
+ Sets on one path is read a second time.
479
500
  - With `depth`, values below the limit are returned as they are: the result shares them
480
501
  with the input instead of copying them. An object reached at several levels is
481
502
  transformed once per level.
482
503
  - With `exclude`, an object reached through several keys is transformed once for each
483
504
  set of paths that match below them: one result when the same paths continue below
484
505
  every key, one per set otherwise.
485
- - The transformation is recursive: data nested about a thousand levels deep or more can
486
- exceed the JavaScript call stack and throw an error (usually a `RangeError`).
506
+ - The transformation is recursive, so very deeply nested data hits a limit: see
507
+ [Limits](#limits).
508
+
509
+ ---
510
+
511
+ ## Limits
512
+
513
+ Measured with Node 24 on an Apple M4 Pro, with Node's default stack and memory sizes:
514
+
515
+ - **Nesting depth.** The conversion is recursive. Depending on the shape, the first call
516
+ in a process converts 1,300 to 2,400 levels of nesting (nested Maps the fewest, nested
517
+ objects the most), and once the code is warm about 2,600 (nested arrays) to 4,400;
518
+ deeper data throws a `RangeError`. API data nests far less, but untrusted input can nest
519
+ deeper (`JSON.parse` accepts millions of levels), so catch the error where such input is
520
+ converted.
521
+ - **Size.** There is no fixed limit on keys, objects or array items. The result is a full
522
+ copy, so the input and the result must both fit in memory: converting 1 million records
523
+ with 30 million keys (725 MB in memory) needed 1.5 GB more at its peak, and 1.1 GB with
524
+ `keepReferences: false`. When memory runs out, Node ends the process, which cannot be
525
+ caught.
526
+ - **Long keys.** Conversion time grows linearly with a key's length: about 20 ms for a key
527
+ of a million characters.
487
528
 
488
529
  ---
489
530
 
@@ -494,7 +535,8 @@ stable. Key outputs change only to fix bugs, and every change is listed in the
494
535
  [changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md): if you
495
536
  store converted keys, such as database columns, check it before upgrading. A minor
496
537
  version can add an option, which `Required<TransformOptions>` and
497
- `Required<TransformKeyOptions>` then require.
538
+ `Required<TransformKeyOptions>` then require, or a target case, which code that handles
539
+ every member of `StringCase` then has to handle too.
498
540
 
499
541
  ### Upgrading to v3
500
542