deep-case-crafter 2.1.0 → 2.1.1
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 +35 -19
- package/dist/index.d.mts +35 -19
- package/dist/index.d.ts +35 -19
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -2
- package/readme.md +8 -0
package/dist/index.d.cts
CHANGED
|
@@ -13,28 +13,25 @@ interface TransformOptions {
|
|
|
13
13
|
*/
|
|
14
14
|
targetCase?: StringCase;
|
|
15
15
|
/**
|
|
16
|
-
* The source casing format
|
|
16
|
+
* The source casing format.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* - Ensures consistent results if input has mixed casing
|
|
18
|
+
* This is a **type-level assertion only**, used (together with `targetCase`) to
|
|
19
|
+
* produce precise output types. It does **not** change runtime behavior: at runtime
|
|
20
|
+
* every key's case is auto-detected individually whether or not `sourceCase` is set.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
22
|
+
* Because of that, the precise types are accurate only for keys that are genuinely
|
|
23
|
+
* well-formed in the declared source case. For ambiguous keys — mixed case
|
|
24
|
+
* (`mixed_Case`), SCREAMING_SNAKE (`FOO_BAR`), mixed separators (`a_b-c`), or
|
|
25
|
+
* non-ASCII (`café_table`) — the runtime preserves the key unchanged, while the type
|
|
26
|
+
* may show it transformed. Set `sourceCase` only when your keys really are that case.
|
|
27
|
+
*
|
|
28
|
+
* When omitted, the result type is `unknown` (the runtime still auto-detects each key).
|
|
27
29
|
*
|
|
28
30
|
* @example
|
|
29
|
-
* //
|
|
31
|
+
* // Precise types when both are set and keys are well-formed snake_case:
|
|
30
32
|
* const result = transform(data, { targetCase: 'camel', sourceCase: 'snake' });
|
|
31
|
-
* // result has exact transformed types
|
|
32
|
-
*
|
|
33
|
-
* // Without source - flexible runtime behavior
|
|
34
|
-
* const result = transform(data, { targetCase: 'camel' });
|
|
35
|
-
* // TypeScript types are more general
|
|
36
33
|
*
|
|
37
|
-
* @default undefined
|
|
34
|
+
* @default undefined
|
|
38
35
|
*/
|
|
39
36
|
sourceCase?: StringCase;
|
|
40
37
|
}
|
|
@@ -72,8 +69,14 @@ type IsUpper<C extends string> = C extends Uppercase<C> ? (C extends Lowercase<C
|
|
|
72
69
|
type IsLower<C extends string> = C extends Lowercase<C> ? (C extends Uppercase<C> ? false : true) : false;
|
|
73
70
|
type IsLowerOrDigit<C extends string> = IsLower<C> extends true ? true : C extends `${number}` ? true : false;
|
|
74
71
|
type FirstChar<S extends string> = S extends `${infer F}${string}` ? F : '';
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Whether an uppercase `C` at the current position begins a new word (so a
|
|
74
|
+
* separator goes before it): true when the previous char is lower/digit, or when
|
|
75
|
+
* `C` ends an acronym run (two preceding uppercase chars) followed by a lowercase.
|
|
76
|
+
*/
|
|
77
|
+
type NeedsSeparator<C extends string, Prev extends string, PrevPrev extends string, Next extends string> = IsUpper<C> extends true ? IsLowerOrDigit<Prev> extends true ? true : IsUpper<Prev> extends true ? IsUpper<PrevPrev> extends true ? IsLower<Next> : false : false : false;
|
|
78
|
+
type SeparatorScan<S extends string, Sep extends string, Prev extends string = '', PrevPrev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer R}` ? SeparatorScan<R, Sep, C, Prev, `${Acc}${NeedsSeparator<C, Prev, PrevPrev, FirstChar<R>> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
|
|
79
|
+
type SeparatorCase<S extends string, Sep extends string> = SeparatorScan<S, Sep>;
|
|
77
80
|
/**
|
|
78
81
|
* Type utility that transforms a camelCase string to snake_case (acronym-aware)
|
|
79
82
|
* Example: 'helloWorld' -> 'hello_world', 'getHTTPResponse' -> 'get_http_response'
|
|
@@ -135,7 +138,20 @@ type KebabToSnake<S extends string> = S extends `${infer P}-${infer R}` ? `${P}_
|
|
|
135
138
|
*/
|
|
136
139
|
type ForbiddenStartChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
137
140
|
type ForbiddenEndChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
138
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The character set the runtime is willing to transform. Any key containing a
|
|
143
|
+
* character outside it — a symbol, or a non-ASCII letter like `é` — is preserved
|
|
144
|
+
* wholesale at runtime (`/[^A-Za-z0-9_-]/`), so the type must preserve it too.
|
|
145
|
+
* An explicit ASCII set is required because TS's `Uppercase`/`Lowercase` are
|
|
146
|
+
* Unicode-aware and would otherwise treat `é` as an ordinary transformable letter.
|
|
147
|
+
*/
|
|
148
|
+
type AllowedKeyChar = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '_' | '-';
|
|
149
|
+
/**
|
|
150
|
+
* True if `S` contains any character outside `AllowedKeyChar`. Mirrors the runtime's
|
|
151
|
+
* `/[^A-Za-z0-9_-]/` preserve check.
|
|
152
|
+
*/
|
|
153
|
+
type HasOutOfCharsetChar<S extends string> = S extends `${infer C}${infer R}` ? C extends AllowedKeyChar ? HasOutOfCharsetChar<R> : true : false;
|
|
154
|
+
type PreservedKey<K extends string> = HasOutOfCharsetChar<K> extends true ? true : K extends `${infer First}${string}` ? First extends ForbiddenStartChars ? true : K extends `${string}${infer Last}` ? Last extends ForbiddenEndChars ? true : K extends `${string}__${string}` | `${string}--${string}` ? true : false : false : false;
|
|
139
155
|
/**
|
|
140
156
|
* Type utility that selects the correct string transformation based on source and target casing
|
|
141
157
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -13,28 +13,25 @@ interface TransformOptions {
|
|
|
13
13
|
*/
|
|
14
14
|
targetCase?: StringCase;
|
|
15
15
|
/**
|
|
16
|
-
* The source casing format
|
|
16
|
+
* The source casing format.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* - Ensures consistent results if input has mixed casing
|
|
18
|
+
* This is a **type-level assertion only**, used (together with `targetCase`) to
|
|
19
|
+
* produce precise output types. It does **not** change runtime behavior: at runtime
|
|
20
|
+
* every key's case is auto-detected individually whether or not `sourceCase` is set.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
22
|
+
* Because of that, the precise types are accurate only for keys that are genuinely
|
|
23
|
+
* well-formed in the declared source case. For ambiguous keys — mixed case
|
|
24
|
+
* (`mixed_Case`), SCREAMING_SNAKE (`FOO_BAR`), mixed separators (`a_b-c`), or
|
|
25
|
+
* non-ASCII (`café_table`) — the runtime preserves the key unchanged, while the type
|
|
26
|
+
* may show it transformed. Set `sourceCase` only when your keys really are that case.
|
|
27
|
+
*
|
|
28
|
+
* When omitted, the result type is `unknown` (the runtime still auto-detects each key).
|
|
27
29
|
*
|
|
28
30
|
* @example
|
|
29
|
-
* //
|
|
31
|
+
* // Precise types when both are set and keys are well-formed snake_case:
|
|
30
32
|
* const result = transform(data, { targetCase: 'camel', sourceCase: 'snake' });
|
|
31
|
-
* // result has exact transformed types
|
|
32
|
-
*
|
|
33
|
-
* // Without source - flexible runtime behavior
|
|
34
|
-
* const result = transform(data, { targetCase: 'camel' });
|
|
35
|
-
* // TypeScript types are more general
|
|
36
33
|
*
|
|
37
|
-
* @default undefined
|
|
34
|
+
* @default undefined
|
|
38
35
|
*/
|
|
39
36
|
sourceCase?: StringCase;
|
|
40
37
|
}
|
|
@@ -72,8 +69,14 @@ type IsUpper<C extends string> = C extends Uppercase<C> ? (C extends Lowercase<C
|
|
|
72
69
|
type IsLower<C extends string> = C extends Lowercase<C> ? (C extends Uppercase<C> ? false : true) : false;
|
|
73
70
|
type IsLowerOrDigit<C extends string> = IsLower<C> extends true ? true : C extends `${number}` ? true : false;
|
|
74
71
|
type FirstChar<S extends string> = S extends `${infer F}${string}` ? F : '';
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Whether an uppercase `C` at the current position begins a new word (so a
|
|
74
|
+
* separator goes before it): true when the previous char is lower/digit, or when
|
|
75
|
+
* `C` ends an acronym run (two preceding uppercase chars) followed by a lowercase.
|
|
76
|
+
*/
|
|
77
|
+
type NeedsSeparator<C extends string, Prev extends string, PrevPrev extends string, Next extends string> = IsUpper<C> extends true ? IsLowerOrDigit<Prev> extends true ? true : IsUpper<Prev> extends true ? IsUpper<PrevPrev> extends true ? IsLower<Next> : false : false : false;
|
|
78
|
+
type SeparatorScan<S extends string, Sep extends string, Prev extends string = '', PrevPrev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer R}` ? SeparatorScan<R, Sep, C, Prev, `${Acc}${NeedsSeparator<C, Prev, PrevPrev, FirstChar<R>> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
|
|
79
|
+
type SeparatorCase<S extends string, Sep extends string> = SeparatorScan<S, Sep>;
|
|
77
80
|
/**
|
|
78
81
|
* Type utility that transforms a camelCase string to snake_case (acronym-aware)
|
|
79
82
|
* Example: 'helloWorld' -> 'hello_world', 'getHTTPResponse' -> 'get_http_response'
|
|
@@ -135,7 +138,20 @@ type KebabToSnake<S extends string> = S extends `${infer P}-${infer R}` ? `${P}_
|
|
|
135
138
|
*/
|
|
136
139
|
type ForbiddenStartChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
137
140
|
type ForbiddenEndChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
138
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The character set the runtime is willing to transform. Any key containing a
|
|
143
|
+
* character outside it — a symbol, or a non-ASCII letter like `é` — is preserved
|
|
144
|
+
* wholesale at runtime (`/[^A-Za-z0-9_-]/`), so the type must preserve it too.
|
|
145
|
+
* An explicit ASCII set is required because TS's `Uppercase`/`Lowercase` are
|
|
146
|
+
* Unicode-aware and would otherwise treat `é` as an ordinary transformable letter.
|
|
147
|
+
*/
|
|
148
|
+
type AllowedKeyChar = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '_' | '-';
|
|
149
|
+
/**
|
|
150
|
+
* True if `S` contains any character outside `AllowedKeyChar`. Mirrors the runtime's
|
|
151
|
+
* `/[^A-Za-z0-9_-]/` preserve check.
|
|
152
|
+
*/
|
|
153
|
+
type HasOutOfCharsetChar<S extends string> = S extends `${infer C}${infer R}` ? C extends AllowedKeyChar ? HasOutOfCharsetChar<R> : true : false;
|
|
154
|
+
type PreservedKey<K extends string> = HasOutOfCharsetChar<K> extends true ? true : K extends `${infer First}${string}` ? First extends ForbiddenStartChars ? true : K extends `${string}${infer Last}` ? Last extends ForbiddenEndChars ? true : K extends `${string}__${string}` | `${string}--${string}` ? true : false : false : false;
|
|
139
155
|
/**
|
|
140
156
|
* Type utility that selects the correct string transformation based on source and target casing
|
|
141
157
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -13,28 +13,25 @@ interface TransformOptions {
|
|
|
13
13
|
*/
|
|
14
14
|
targetCase?: StringCase;
|
|
15
15
|
/**
|
|
16
|
-
* The source casing format
|
|
16
|
+
* The source casing format.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* - Ensures consistent results if input has mixed casing
|
|
18
|
+
* This is a **type-level assertion only**, used (together with `targetCase`) to
|
|
19
|
+
* produce precise output types. It does **not** change runtime behavior: at runtime
|
|
20
|
+
* every key's case is auto-detected individually whether or not `sourceCase` is set.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
22
|
+
* Because of that, the precise types are accurate only for keys that are genuinely
|
|
23
|
+
* well-formed in the declared source case. For ambiguous keys — mixed case
|
|
24
|
+
* (`mixed_Case`), SCREAMING_SNAKE (`FOO_BAR`), mixed separators (`a_b-c`), or
|
|
25
|
+
* non-ASCII (`café_table`) — the runtime preserves the key unchanged, while the type
|
|
26
|
+
* may show it transformed. Set `sourceCase` only when your keys really are that case.
|
|
27
|
+
*
|
|
28
|
+
* When omitted, the result type is `unknown` (the runtime still auto-detects each key).
|
|
27
29
|
*
|
|
28
30
|
* @example
|
|
29
|
-
* //
|
|
31
|
+
* // Precise types when both are set and keys are well-formed snake_case:
|
|
30
32
|
* const result = transform(data, { targetCase: 'camel', sourceCase: 'snake' });
|
|
31
|
-
* // result has exact transformed types
|
|
32
|
-
*
|
|
33
|
-
* // Without source - flexible runtime behavior
|
|
34
|
-
* const result = transform(data, { targetCase: 'camel' });
|
|
35
|
-
* // TypeScript types are more general
|
|
36
33
|
*
|
|
37
|
-
* @default undefined
|
|
34
|
+
* @default undefined
|
|
38
35
|
*/
|
|
39
36
|
sourceCase?: StringCase;
|
|
40
37
|
}
|
|
@@ -72,8 +69,14 @@ type IsUpper<C extends string> = C extends Uppercase<C> ? (C extends Lowercase<C
|
|
|
72
69
|
type IsLower<C extends string> = C extends Lowercase<C> ? (C extends Uppercase<C> ? false : true) : false;
|
|
73
70
|
type IsLowerOrDigit<C extends string> = IsLower<C> extends true ? true : C extends `${number}` ? true : false;
|
|
74
71
|
type FirstChar<S extends string> = S extends `${infer F}${string}` ? F : '';
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Whether an uppercase `C` at the current position begins a new word (so a
|
|
74
|
+
* separator goes before it): true when the previous char is lower/digit, or when
|
|
75
|
+
* `C` ends an acronym run (two preceding uppercase chars) followed by a lowercase.
|
|
76
|
+
*/
|
|
77
|
+
type NeedsSeparator<C extends string, Prev extends string, PrevPrev extends string, Next extends string> = IsUpper<C> extends true ? IsLowerOrDigit<Prev> extends true ? true : IsUpper<Prev> extends true ? IsUpper<PrevPrev> extends true ? IsLower<Next> : false : false : false;
|
|
78
|
+
type SeparatorScan<S extends string, Sep extends string, Prev extends string = '', PrevPrev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer R}` ? SeparatorScan<R, Sep, C, Prev, `${Acc}${NeedsSeparator<C, Prev, PrevPrev, FirstChar<R>> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
|
|
79
|
+
type SeparatorCase<S extends string, Sep extends string> = SeparatorScan<S, Sep>;
|
|
77
80
|
/**
|
|
78
81
|
* Type utility that transforms a camelCase string to snake_case (acronym-aware)
|
|
79
82
|
* Example: 'helloWorld' -> 'hello_world', 'getHTTPResponse' -> 'get_http_response'
|
|
@@ -135,7 +138,20 @@ type KebabToSnake<S extends string> = S extends `${infer P}-${infer R}` ? `${P}_
|
|
|
135
138
|
*/
|
|
136
139
|
type ForbiddenStartChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
137
140
|
type ForbiddenEndChars = '_' | '-' | '$' | '&' | '%' | '#' | '@' | '!' | '*' | '+' | '=' | '~' | '`' | '^' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' | ':' | ';' | '"' | "'" | '<' | '>' | ',' | '.' | '?' | '/' | `${number}`;
|
|
138
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The character set the runtime is willing to transform. Any key containing a
|
|
143
|
+
* character outside it — a symbol, or a non-ASCII letter like `é` — is preserved
|
|
144
|
+
* wholesale at runtime (`/[^A-Za-z0-9_-]/`), so the type must preserve it too.
|
|
145
|
+
* An explicit ASCII set is required because TS's `Uppercase`/`Lowercase` are
|
|
146
|
+
* Unicode-aware and would otherwise treat `é` as an ordinary transformable letter.
|
|
147
|
+
*/
|
|
148
|
+
type AllowedKeyChar = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '_' | '-';
|
|
149
|
+
/**
|
|
150
|
+
* True if `S` contains any character outside `AllowedKeyChar`. Mirrors the runtime's
|
|
151
|
+
* `/[^A-Za-z0-9_-]/` preserve check.
|
|
152
|
+
*/
|
|
153
|
+
type HasOutOfCharsetChar<S extends string> = S extends `${infer C}${infer R}` ? C extends AllowedKeyChar ? HasOutOfCharsetChar<R> : true : false;
|
|
154
|
+
type PreservedKey<K extends string> = HasOutOfCharsetChar<K> extends true ? true : K extends `${infer First}${string}` ? First extends ForbiddenStartChars ? true : K extends `${string}${infer Last}` ? Last extends ForbiddenEndChars ? true : K extends `${string}__${string}` | `${string}--${string}` ? true : false : false : false;
|
|
139
155
|
/**
|
|
140
156
|
* Type utility that selects the correct string transformation based on source and target casing
|
|
141
157
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const e=e=>e>="A"&&e<="Z",t=e=>e>="a"&&e<="z",
|
|
1
|
+
"use strict";const e=e=>e>="A"&&e<="Z",t=e=>e>="a"&&e<="z",n=e=>e>="0"&&e<="9";function r(r,s){var o,a,c;if(!r)return r;let u="";for(let i=0;i<r.length;i++){const l=r[i];if(e(l)){const l=null!==(o=r[i-1])&&void 0!==o?o:"",f=t(l)||n(l),p=e(l)&&e(null!==(a=r[i-2])&&void 0!==a?a:"")&&t(null!==(c=r[i+1])&&void 0!==c?c:"");(f||p)&&(u+=s)}u+=l.toLowerCase()}return u}const s={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 r(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return r(e,"-")},camel:e=>e},pascal:{snake:function(e){return r(e,"_")},camel:function(e){return e?e.charAt(0).toLowerCase()+e.slice(1):e},kebab:function(e){return r(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}};const o=/^[a-z0-9_]+$/,a=/__+/,c=/^[a-z][a-zA-Z0-9]*$/,u=/^[A-Z][a-zA-Z0-9]*$/,i=/^[a-z0-9-]+$/,l=/-{2,}/,f=/[A-Z]/,p=/[a-z]/,g=/^[a-z0-9]+$/;function d(e){return"string"!=typeof e?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()}(e)?"single":function(e){return"string"==typeof e&&o.test(e)&&!a.test(e)&&(e.includes("_")||g.test(e))}(e)?"snake":function(e){return"string"==typeof e&&c.test(e)&&f.test(e)}(e)?"camel":function(e){return"string"==typeof e&&u.test(e)&&p.test(e)}(e)?"pascal":function(e){return"string"==typeof e&&i.test(e)&&!l.test(e)&&(e.includes("-")||g.test(e))}(e)?"kebab":null}function b(e,t){if(!e)return e;const{targetCase:n}=t,{string:r,shouldTransform:o,sourceCase:a,singleWord:c}=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))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=d(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);if(!o)return r;if(c)return 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}}(r,n);if(!a||a===n)return r;const u=s[a];if(!u)return r;const i=u[n];return i?i(r):r}function C(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}function h(e,t,n=new WeakMap,r=0){var s;if(null==e||"object"!=typeof e)return e;if(n.has(e))return n.get(e);const o=null!==(s=t.depth)&&void 0!==s?s:1/0;if(r>=o)return e;const a=t.depth===o?t:Object.assign(Object.assign({},t),{depth:o});let c;if(Array.isArray(e))c=function(e,t,n){const r=[];t(r);for(const t of e)r.push(n(t));return r}(e,t=>n.set(e,t),e=>h(e,a,n,r));else if(e instanceof Map){c=function(e,t){if(!(e instanceof Map))return e;const n=[];for(const[r,s]of e.entries())if("string"==typeof r){const e=b(r,t);n.push([e,s])}else n.push([r,s]);return new Map(n)}(e,t),n.set(e,c);const s=c;for(const[e,t]of s.entries())null!==t&&"object"==typeof t&&s.set(e,h(t,a,n,r+1))}else if(e instanceof Set){c=new Set(e),n.set(e,c);const t=c,s=Array.from(t);t.clear();for(const e of s)null!==e&&"object"==typeof e?t.add(h(e,a,n,r+1)):t.add(e)}else{c=function(e,t){if(null===e||"object"!=typeof e||Array.isArray(e))return e;const{targetCase:n,sourceCase:r}=t,s={};for(const t in e)Object.prototype.hasOwnProperty.call(e,t)&&C(s,isNaN(Number(t))||String(Number(t))!==t?b(t,{targetCase:n}):Number(t),e[t]);for(const t of Object.getOwnPropertySymbols(e))s[t]=e[t];return s}(e,t),n.set(e,c);const s=c;for(const e in s){const t=s[e];null!==t&&"object"==typeof t&&C(s,e,h(t,a,n,r+1))}}return c}exports.transform=function(e,t){var n;return h(e,Object.assign(Object.assign({},t),{targetCase:(null==t?void 0:t.targetCase)||"camel",depth:null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0}))};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=e=>e>="A"&&e<="Z",t=e=>e>="a"&&e<="z",n=e=>e>="0"&&e<="9";function r(r,s){var o,a,c;if(!r)return r;let u="";for(let i=0;i<r.length;i++){const l=r[i];if(e(l)){const l=null!==(o=r[i-1])&&void 0!==o?o:"",f=t(l)||n(l),p=e(l)&&e(null!==(a=r[i-2])&&void 0!==a?a:"")&&t(null!==(c=r[i+1])&&void 0!==c?c:"");(f||p)&&(u+=s)}u+=l.toLowerCase()}return u}const s={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 r(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return r(e,"-")},camel:e=>e},pascal:{snake:function(e){return r(e,"_")},camel:function(e){return e?e.charAt(0).toLowerCase()+e.slice(1):e},kebab:function(e){return r(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}};const o=/^[a-z0-9_]+$/,a=/__+/,c=/^[a-z][a-zA-Z0-9]*$/,u=/^[A-Z][a-zA-Z0-9]*$/,i=/^[a-z0-9-]+$/,l=/-{2,}/,f=/[A-Z]/,p=/[a-z]/,g=/^[a-z0-9]+$/;function d(e){return"string"!=typeof e?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()}(e)?"single":function(e){return"string"==typeof e&&o.test(e)&&!a.test(e)&&(e.includes("_")||g.test(e))}(e)?"snake":function(e){return"string"==typeof e&&c.test(e)&&f.test(e)}(e)?"camel":function(e){return"string"==typeof e&&u.test(e)&&p.test(e)}(e)?"pascal":function(e){return"string"==typeof e&&i.test(e)&&!l.test(e)&&(e.includes("-")||g.test(e))}(e)?"kebab":null}function
|
|
1
|
+
const e=e=>e>="A"&&e<="Z",t=e=>e>="a"&&e<="z",n=e=>e>="0"&&e<="9";function r(r,s){var o,a,c;if(!r)return r;let u="";for(let i=0;i<r.length;i++){const l=r[i];if(e(l)){const l=null!==(o=r[i-1])&&void 0!==o?o:"",f=t(l)||n(l),p=e(l)&&e(null!==(a=r[i-2])&&void 0!==a?a:"")&&t(null!==(c=r[i+1])&&void 0!==c?c:"");(f||p)&&(u+=s)}u+=l.toLowerCase()}return u}const s={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 r(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return r(e,"-")},camel:e=>e},pascal:{snake:function(e){return r(e,"_")},camel:function(e){return e?e.charAt(0).toLowerCase()+e.slice(1):e},kebab:function(e){return r(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}};const o=/^[a-z0-9_]+$/,a=/__+/,c=/^[a-z][a-zA-Z0-9]*$/,u=/^[A-Z][a-zA-Z0-9]*$/,i=/^[a-z0-9-]+$/,l=/-{2,}/,f=/[A-Z]/,p=/[a-z]/,g=/^[a-z0-9]+$/;function d(e){return"string"!=typeof e?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()}(e)?"single":function(e){return"string"==typeof e&&o.test(e)&&!a.test(e)&&(e.includes("_")||g.test(e))}(e)?"snake":function(e){return"string"==typeof e&&c.test(e)&&f.test(e)}(e)?"camel":function(e){return"string"==typeof e&&u.test(e)&&p.test(e)}(e)?"pascal":function(e){return"string"==typeof e&&i.test(e)&&!l.test(e)&&(e.includes("-")||g.test(e))}(e)?"kebab":null}function b(e,t){if(!e)return e;const{targetCase:n}=t,{string:r,shouldTransform:o,sourceCase:a,singleWord:c}=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))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=d(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);if(!o)return r;if(c)return 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}}(r,n);if(!a||a===n)return r;const u=s[a];if(!u)return r;const i=u[n];return i?i(r):r}function C(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}function h(e,t,n=new WeakMap,r=0){var s;if(null==e||"object"!=typeof e)return e;if(n.has(e))return n.get(e);const o=null!==(s=t.depth)&&void 0!==s?s:1/0;if(r>=o)return e;const a=t.depth===o?t:Object.assign(Object.assign({},t),{depth:o});let c;if(Array.isArray(e))c=function(e,t,n){const r=[];t(r);for(const t of e)r.push(n(t));return r}(e,t=>n.set(e,t),e=>h(e,a,n,r));else if(e instanceof Map){c=function(e,t){if(!(e instanceof Map))return e;const n=[];for(const[r,s]of e.entries())if("string"==typeof r){const e=b(r,t);n.push([e,s])}else n.push([r,s]);return new Map(n)}(e,t),n.set(e,c);const s=c;for(const[e,t]of s.entries())null!==t&&"object"==typeof t&&s.set(e,h(t,a,n,r+1))}else if(e instanceof Set){c=new Set(e),n.set(e,c);const t=c,s=Array.from(t);t.clear();for(const e of s)null!==e&&"object"==typeof e?t.add(h(e,a,n,r+1)):t.add(e)}else{c=function(e,t){if(null===e||"object"!=typeof e||Array.isArray(e))return e;const{targetCase:n,sourceCase:r}=t,s={};for(const t in e)Object.prototype.hasOwnProperty.call(e,t)&&C(s,isNaN(Number(t))||String(Number(t))!==t?b(t,{targetCase:n}):Number(t),e[t]);for(const t of Object.getOwnPropertySymbols(e))s[t]=e[t];return s}(e,t),n.set(e,c);const s=c;for(const e in s){const t=s[e];null!==t&&"object"==typeof t&&C(s,e,h(t,a,n,r+1))}}return c}function y(e,t){var n;return h(e,Object.assign(Object.assign({},t),{targetCase:(null==t?void 0:t.targetCase)||"camel",depth:null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0}))}export{y as transform};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-case-crafter",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Transforms deeply nested object, array, Map, and Set keys between common case formats while preserving TypeScript type safety",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -60,10 +60,12 @@
|
|
|
60
60
|
"prepare": "husky",
|
|
61
61
|
"test": "jest",
|
|
62
62
|
"test-coverage": "npm test -- --coverage",
|
|
63
|
+
"test:types": "tsc --noEmit",
|
|
63
64
|
"tsc": "tsc --noEmit",
|
|
64
65
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src/ --fix --max-warnings=0 --no-cache",
|
|
65
66
|
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,md}\"",
|
|
66
|
-
"benchmark": "tsx benchmark/*.benchmark.ts"
|
|
67
|
+
"benchmark": "tsx benchmark/*.benchmark.ts",
|
|
68
|
+
"check": "npm run lint && npm run test:types && npm run test-coverage && npm run build"
|
|
67
69
|
},
|
|
68
70
|
"devDependencies": {
|
|
69
71
|
"@eslint/js": "10.0.1",
|
package/readme.md
CHANGED
|
@@ -177,6 +177,14 @@ set, so `result.get_http_response` is known at compile time.
|
|
|
177
177
|
|
|
178
178
|
> **TypeScript inference:** precise output types are produced only when both `sourceCase` and `targetCase` are passed. Without both, the call still works at runtime (auto-detecting each key) but the result type is `unknown`.
|
|
179
179
|
|
|
180
|
+
> **`sourceCase` is a type-level assertion, not a runtime switch.** At runtime every
|
|
181
|
+
> key's case is auto-detected regardless of `sourceCase`, so the precise types are
|
|
182
|
+
> accurate only for keys genuinely well-formed in the declared source case. Ambiguous
|
|
183
|
+
> keys — mixed case (`mixed_Case`), `SCREAMING_SNAKE`, or mixed separators (`a_b-c`) — are
|
|
184
|
+
> **preserved unchanged at runtime**, even though the type may show them transformed. Set
|
|
185
|
+
> `sourceCase` only when your keys really are that case. (Out-of-charset keys such as
|
|
186
|
+
> `café_table` or `a.b_c` are preserved at both layers.)
|
|
187
|
+
|
|
180
188
|
---
|
|
181
189
|
|
|
182
190
|
## Advanced: explicit output type overload
|