deep-case-crafter 3.1.0 → 3.2.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
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
27
27
  type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
28
28
 
29
29
  /**
30
- * Options for the transform function
30
+ * Options for the transformKey function. The transform function takes them as well.
31
31
  */
32
- interface TransformOptions {
32
+ interface TransformKeyOptions {
33
33
  /**
34
34
  * The target casing format to transform to
35
35
  * @default 'camel'
36
36
  */
37
37
  targetCase?: StringCase | undefined;
38
+ }
39
+ /**
40
+ * Options for the transform function
41
+ */
42
+ interface TransformOptions extends TransformKeyOptions {
38
43
  /**
39
44
  * How many nesting levels to transform: `1` transforms only the top-level keys, `2`
40
45
  * also the keys one level down, and so on. The values of objects, Maps and Sets are one
@@ -69,13 +74,24 @@ interface TransformOptions {
69
74
  sourceCase?: StringCase | undefined;
70
75
  }
71
76
  /**
72
- * Rejects option names `transform` does not know. The options object is inferred as a
73
- * whole, so without this an object literal would accept any extra property, typos such
74
- * as `depht` included. The error names the unknown option.
77
+ * Rejects option names that are not in `Known` (by default, the options of `transform`).
78
+ * The options object is inferred as a whole, so without this an object literal would
79
+ * accept any extra property, typos such as `depht` included. The error names the
80
+ * unknown option.
75
81
  */
76
- type NoUnknownOptions<Options> = {
77
- [K in Exclude<keyof Options, keyof TransformOptions>]: `Unknown option: ${K & string}`;
82
+ type NoUnknownOptions<Options, Known = TransformOptions> = {
83
+ [K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
78
84
  };
85
+ /**
86
+ * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
87
+ * The options parameter is then `TransformKeyOptions` itself rather than an
88
+ * intersection, so TypeScript checks the argument for a property in common with it, as
89
+ * it does for every type whose properties are all optional: `'snake'`, an array, or a
90
+ * stored options object with only a misspelled option is rejected.
91
+ */
92
+ type NoUnknownKeyOptions<Options> = [
93
+ Exclude<keyof Options, keyof TransformKeyOptions>
94
+ ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
79
95
  /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
80
96
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
81
97
  /**
@@ -211,10 +227,33 @@ type FormatKey<K extends string, From extends StringCase, To extends StringCase>
211
227
  /** Applies the detected case of `K` (`transformCase`) */
212
228
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
213
229
  /**
214
- * The key a string key `K` becomes when transformed to `To`. A `string` or
230
+ * One key `K` (not a union) transformed to one plain target case `To`. A `string` or
215
231
  * template-literal key cannot be known exactly, so it becomes `string`.
216
232
  */
217
- type TransformKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
233
+ type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
234
+ /**
235
+ * One key `K` transformed to each member of a union of plain target cases `To`.
236
+ *
237
+ * Use `TransformKey` instead. This type is exported only so that declaration files can
238
+ * refer to it: for a fixed key and a generic target case, as in
239
+ * `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
240
+ * the key type is written in terms of it. Without a name to refer to, the compiler
241
+ * writes out its recursive steps, and gets them wrong.
242
+ */
243
+ type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
244
+ /**
245
+ * The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
246
+ * `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
247
+ * so it becomes `string`. A union of keys or of target cases gives the union of their
248
+ * results, and a string enum member or branded literal stands for its case.
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
253
+ * // => 'created_at' | 'user_id'
254
+ * ```
255
+ */
256
+ type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
218
257
 
219
258
  /** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
220
259
  type Primitive = string | number | boolean | bigint | symbol | null | undefined;
@@ -403,6 +442,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
403
442
  * result: `unknown`.
404
443
  */
405
444
  type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : 'exclude' extends keyof Options ? TransformResultForExcluding<T, Options> : TransformResultForOne<T, Options> : never;
445
+ /**
446
+ * The key type `transformKey` returns for an options object. Each member of a union of
447
+ * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
+ * 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.
450
+ *
451
+ * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
+ * instead, because options with a generic type do not match the overload that returns
453
+ * it.
454
+ */
455
+ type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
406
456
 
407
457
  /**
408
458
  * Transforms the keys of `data` to camelCase, at every nesting level.
@@ -467,5 +517,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
467
517
  */
468
518
  declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
469
519
 
470
- export { transform };
471
- export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
520
+ /**
521
+ * 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).
524
+ *
525
+ * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
+ */
527
+ declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
+ /**
529
+ * 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).
533
+ *
534
+ * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
+ * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
536
+ * and a `string` key gives `string`.
537
+ *
538
+ * `Options` captures the options object as a whole, as `transform` does, so each member
539
+ * of a union of options objects gets its own result, and an optional `targetCase` also
540
+ * gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
541
+ * `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
542
+ * value with no property in common with it, such as `'snake'`, is rejected as well. An
543
+ * options object that may itself be `undefined` does not match this overload, and gets
544
+ * a `string` result type from the last one.
545
+ */
546
+ declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
547
+ /**
548
+ * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
549
+ * every key, typed as a `string`: for an options object that may be `undefined`, or one
550
+ * with properties other than `targetCase`.
551
+ */
552
+ declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
+
554
+ export { transform, transformKey };
555
+ export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
package/dist/index.d.mts CHANGED
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
27
27
  type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
28
28
 
29
29
  /**
30
- * Options for the transform function
30
+ * Options for the transformKey function. The transform function takes them as well.
31
31
  */
32
- interface TransformOptions {
32
+ interface TransformKeyOptions {
33
33
  /**
34
34
  * The target casing format to transform to
35
35
  * @default 'camel'
36
36
  */
37
37
  targetCase?: StringCase | undefined;
38
+ }
39
+ /**
40
+ * Options for the transform function
41
+ */
42
+ interface TransformOptions extends TransformKeyOptions {
38
43
  /**
39
44
  * How many nesting levels to transform: `1` transforms only the top-level keys, `2`
40
45
  * also the keys one level down, and so on. The values of objects, Maps and Sets are one
@@ -69,13 +74,24 @@ interface TransformOptions {
69
74
  sourceCase?: StringCase | undefined;
70
75
  }
71
76
  /**
72
- * Rejects option names `transform` does not know. The options object is inferred as a
73
- * whole, so without this an object literal would accept any extra property, typos such
74
- * as `depht` included. The error names the unknown option.
77
+ * Rejects option names that are not in `Known` (by default, the options of `transform`).
78
+ * The options object is inferred as a whole, so without this an object literal would
79
+ * accept any extra property, typos such as `depht` included. The error names the
80
+ * unknown option.
75
81
  */
76
- type NoUnknownOptions<Options> = {
77
- [K in Exclude<keyof Options, keyof TransformOptions>]: `Unknown option: ${K & string}`;
82
+ type NoUnknownOptions<Options, Known = TransformOptions> = {
83
+ [K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
78
84
  };
85
+ /**
86
+ * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
87
+ * The options parameter is then `TransformKeyOptions` itself rather than an
88
+ * intersection, so TypeScript checks the argument for a property in common with it, as
89
+ * it does for every type whose properties are all optional: `'snake'`, an array, or a
90
+ * stored options object with only a misspelled option is rejected.
91
+ */
92
+ type NoUnknownKeyOptions<Options> = [
93
+ Exclude<keyof Options, keyof TransformKeyOptions>
94
+ ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
79
95
  /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
80
96
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
81
97
  /**
@@ -211,10 +227,33 @@ type FormatKey<K extends string, From extends StringCase, To extends StringCase>
211
227
  /** Applies the detected case of `K` (`transformCase`) */
212
228
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
213
229
  /**
214
- * The key a string key `K` becomes when transformed to `To`. A `string` or
230
+ * One key `K` (not a union) transformed to one plain target case `To`. A `string` or
215
231
  * template-literal key cannot be known exactly, so it becomes `string`.
216
232
  */
217
- type TransformKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
233
+ type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
234
+ /**
235
+ * One key `K` transformed to each member of a union of plain target cases `To`.
236
+ *
237
+ * Use `TransformKey` instead. This type is exported only so that declaration files can
238
+ * refer to it: for a fixed key and a generic target case, as in
239
+ * `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
240
+ * the key type is written in terms of it. Without a name to refer to, the compiler
241
+ * writes out its recursive steps, and gets them wrong.
242
+ */
243
+ type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
244
+ /**
245
+ * The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
246
+ * `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
247
+ * so it becomes `string`. A union of keys or of target cases gives the union of their
248
+ * results, and a string enum member or branded literal stands for its case.
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
253
+ * // => 'created_at' | 'user_id'
254
+ * ```
255
+ */
256
+ type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
218
257
 
219
258
  /** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
220
259
  type Primitive = string | number | boolean | bigint | symbol | null | undefined;
@@ -403,6 +442,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
403
442
  * result: `unknown`.
404
443
  */
405
444
  type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : 'exclude' extends keyof Options ? TransformResultForExcluding<T, Options> : TransformResultForOne<T, Options> : never;
445
+ /**
446
+ * The key type `transformKey` returns for an options object. Each member of a union of
447
+ * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
+ * 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.
450
+ *
451
+ * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
+ * instead, because options with a generic type do not match the overload that returns
453
+ * it.
454
+ */
455
+ type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
406
456
 
407
457
  /**
408
458
  * Transforms the keys of `data` to camelCase, at every nesting level.
@@ -467,5 +517,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
467
517
  */
468
518
  declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
469
519
 
470
- export { transform };
471
- export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
520
+ /**
521
+ * 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).
524
+ *
525
+ * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
+ */
527
+ declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
+ /**
529
+ * 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).
533
+ *
534
+ * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
+ * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
536
+ * and a `string` key gives `string`.
537
+ *
538
+ * `Options` captures the options object as a whole, as `transform` does, so each member
539
+ * of a union of options objects gets its own result, and an optional `targetCase` also
540
+ * gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
541
+ * `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
542
+ * value with no property in common with it, such as `'snake'`, is rejected as well. An
543
+ * options object that may itself be `undefined` does not match this overload, and gets
544
+ * a `string` result type from the last one.
545
+ */
546
+ declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
547
+ /**
548
+ * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
549
+ * every key, typed as a `string`: for an options object that may be `undefined`, or one
550
+ * with properties other than `targetCase`.
551
+ */
552
+ declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
+
554
+ export { transform, transformKey };
555
+ export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
package/dist/index.d.ts CHANGED
@@ -27,14 +27,19 @@ type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
27
27
  type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
28
28
 
29
29
  /**
30
- * Options for the transform function
30
+ * Options for the transformKey function. The transform function takes them as well.
31
31
  */
32
- interface TransformOptions {
32
+ interface TransformKeyOptions {
33
33
  /**
34
34
  * The target casing format to transform to
35
35
  * @default 'camel'
36
36
  */
37
37
  targetCase?: StringCase | undefined;
38
+ }
39
+ /**
40
+ * Options for the transform function
41
+ */
42
+ interface TransformOptions extends TransformKeyOptions {
38
43
  /**
39
44
  * How many nesting levels to transform: `1` transforms only the top-level keys, `2`
40
45
  * also the keys one level down, and so on. The values of objects, Maps and Sets are one
@@ -69,13 +74,24 @@ interface TransformOptions {
69
74
  sourceCase?: StringCase | undefined;
70
75
  }
71
76
  /**
72
- * Rejects option names `transform` does not know. The options object is inferred as a
73
- * whole, so without this an object literal would accept any extra property, typos such
74
- * as `depht` included. The error names the unknown option.
77
+ * Rejects option names that are not in `Known` (by default, the options of `transform`).
78
+ * The options object is inferred as a whole, so without this an object literal would
79
+ * accept any extra property, typos such as `depht` included. The error names the
80
+ * unknown option.
75
81
  */
76
- type NoUnknownOptions<Options> = {
77
- [K in Exclude<keyof Options, keyof TransformOptions>]: `Unknown option: ${K & string}`;
82
+ type NoUnknownOptions<Options, Known = TransformOptions> = {
83
+ [K in Exclude<keyof Options, keyof Known>]: `Unknown option: ${K & string}`;
78
84
  };
85
+ /**
86
+ * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
87
+ * The options parameter is then `TransformKeyOptions` itself rather than an
88
+ * intersection, so TypeScript checks the argument for a property in common with it, as
89
+ * it does for every type whose properties are all optional: `'snake'`, an array, or a
90
+ * stored options object with only a misspelled option is rejected.
91
+ */
92
+ type NoUnknownKeyOptions<Options> = [
93
+ Exclude<keyof Options, keyof TransformKeyOptions>
94
+ ] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
79
95
  /** `To` as a plain case literal, or `'camel'` (the default) for `undefined` */
80
96
  type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
81
97
  /**
@@ -211,10 +227,33 @@ type FormatKey<K extends string, From extends StringCase, To extends StringCase>
211
227
  /** Applies the detected case of `K` (`transformCase`) */
212
228
  type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
213
229
  /**
214
- * The key a string key `K` becomes when transformed to `To`. A `string` or
230
+ * One key `K` (not a union) transformed to one plain target case `To`. A `string` or
215
231
  * template-literal key cannot be known exactly, so it becomes `string`.
216
232
  */
217
- type TransformKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
233
+ type TransformOneKey<K extends string, To extends StringCase> = IsStringLiteral<K> extends false ? string : K extends '' ? K : IsPreservedKey<K> extends true ? K : TransformDetectedKey<K, DetectedCase<K>, To>;
234
+ /**
235
+ * One key `K` transformed to each member of a union of plain target cases `To`.
236
+ *
237
+ * Use `TransformKey` instead. This type is exported only so that declaration files can
238
+ * refer to it: for a fixed key and a generic target case, as in
239
+ * `<C extends StringCase>(targetCase: C) => transformKey('created_by_id', { targetCase })`,
240
+ * the key type is written in terms of it. Without a name to refer to, the compiler
241
+ * writes out its recursive steps, and gets them wrong.
242
+ */
243
+ type TransformKeyToEach<K extends string, To extends StringCase> = To extends StringCase ? TransformOneKey<K, To> : never;
244
+ /**
245
+ * The key a string key `K` becomes when transformed to `To` (default `'camel'`), as
246
+ * `transformKey` returns it. A `string` or template-literal key cannot be known exactly,
247
+ * so it becomes `string`. A union of keys or of target cases gives the union of their
248
+ * results, and a string enum member or branded literal stands for its case.
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * type Column = TransformKey<'createdAt' | 'userID', 'snake'>;
253
+ * // => 'created_at' | 'user_id'
254
+ * ```
255
+ */
256
+ type TransformKey<K extends string, To extends StringCase = 'camel'> = K extends string ? TransformKeyToEach<K, PlainCase<To>> : never;
218
257
 
219
258
  /** Primitive values, including branded ones such as `string & { __brand: 'UserId' }` */
220
259
  type Primitive = string | number | boolean | bigint | symbol | null | undefined;
@@ -403,6 +442,17 @@ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer
403
442
  * result: `unknown`.
404
443
  */
405
444
  type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : 'exclude' extends keyof Options ? TransformResultForExcluding<T, Options> : TransformResultForOne<T, Options> : never;
445
+ /**
446
+ * The key type `transformKey` returns for an options object. Each member of a union of
447
+ * options objects gives its own key, and an optional `targetCase` may also be omitted,
448
+ * 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.
450
+ *
451
+ * Not exported: declaration files of code around `transformKey` refer to `TransformKey`
452
+ * instead, because options with a generic type do not match the overload that returns
453
+ * it.
454
+ */
455
+ type TransformKeyFor<K extends string, Options> = Options extends unknown ? TransformKey<K, TargetCaseOf<Options> | (IsOptional<Options, 'targetCase'> extends true ? 'camel' : never)> : never;
406
456
 
407
457
  /**
408
458
  * Transforms the keys of `data` to camelCase, at every nesting level.
@@ -467,5 +517,39 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
467
517
  */
468
518
  declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
469
519
 
470
- export { transform };
471
- export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
520
+ /**
521
+ * 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).
524
+ *
525
+ * The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
526
+ */
527
+ declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
528
+ /**
529
+ * 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).
533
+ *
534
+ * The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
535
+ * is typed `'user_id'`, a union of keys or target cases gives the union of the results,
536
+ * and a `string` key gives `string`.
537
+ *
538
+ * `Options` captures the options object as a whole, as `transform` does, so each member
539
+ * of a union of options objects gets its own result, and an optional `targetCase` also
540
+ * gives the camelCase key. `NoUnknownKeyOptions` rejects option names other than
541
+ * `targetCase`, and otherwise leaves the parameter `TransformKeyOptions` itself, so a
542
+ * value with no property in common with it, such as `'snake'`, is rejected as well. An
543
+ * options object that may itself be `undefined` does not match this overload, and gets
544
+ * a `string` result type from the last one.
545
+ */
546
+ declare function transformKey<K extends string, Options extends TransformKeyOptions = TransformKeyOptions>(key: K, options: Options & NoUnknownKeyOptions<Options>): TransformKeyFor<K, Options>;
547
+ /**
548
+ * Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
549
+ * every key, typed as a `string`: for an options object that may be `undefined`, or one
550
+ * with properties other than `targetCase`.
551
+ */
552
+ declare function transformKey(key: string, options?: TransformKeyOptions): string;
553
+
554
+ export { transform, transformKey };
555
+ export type { 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]/,c=/^[a-z0-9]+$/,i=/^[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},c={options:a,visitedRefs:[],named:o,other:g(n),root:t};return a.exclude=c,c}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 z(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 U(e,t){if(!e)return e;let n="";for(let r=0;r<e.length;r++)z(e,r)&&(n+=t),n+=e[r].toLowerCase();return n}const A={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 U(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return U(e,"-")},camel:e=>e},pascal:{snake:function(e){return U(e,"_")},camel:function(e){if(!e)return e;let t=1;for(;t<e.length&&!z(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)},kebab:function(e){return U(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 _(i){return"string"!=typeof i?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()}(i)?"single":function(n){return"string"==typeof n&&e.test(n)&&!t.test(n)&&(n.includes("_")||c.test(n))}(i)?"snake":function(e){return"string"==typeof e&&n.test(e)&&u.test(e)}(i)?"camel":function(e){return"string"==typeof e&&r.test(e)&&a.test(e)}(i)?"pascal":function(e){return"string"==typeof e&&o.test(e)&&!s.test(e)&&(e.includes("-")||c.test(e))}(i)?"kebab":null}function j(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)||i.test(e))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=_(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?A[o][t](n):n:n}const $=new Map;function O(e,t){var n;if(e.length>100)return j(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:j(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,c=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,c)){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?j(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:j(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,c)){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(c===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"];exports.transform=function(e,t){var n;const r=(null==t?void 0:t.targetCase)||"camel",o=null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0;if(s=r,!M.includes(s))throw new TypeError(`Unsupported targetCase ${f(r)}. Use one of: ${M.join(", ")}.`);var s;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:r,depth:o,exclude:null},a=null==t?void 0:t.exclude,c=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===c?x(e,u):x(e,c.options,c.visitedRefs)};
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))};
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]/,c=/^[a-z0-9]+$/,i=/^[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},c={options:a,visitedRefs:[],named:o,other:g(n),root:t};return a.exclude=c,c}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 z(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 U(e,t){if(!e)return e;let n="";for(let r=0;r<e.length;r++)z(e,r)&&(n+=t),n+=e[r].toLowerCase();return n}const A={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 U(e,"_")},pascal:function(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e},kebab:function(e){return U(e,"-")},camel:e=>e},pascal:{snake:function(e){return U(e,"_")},camel:function(e){if(!e)return e;let t=1;for(;t<e.length&&!z(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)},kebab:function(e){return U(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 _(i){return"string"!=typeof i?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()}(i)?"single":function(n){return"string"==typeof n&&e.test(n)&&!t.test(n)&&(n.includes("_")||c.test(n))}(i)?"snake":function(e){return"string"==typeof e&&n.test(e)&&u.test(e)}(i)?"camel":function(e){return"string"==typeof e&&r.test(e)&&a.test(e)}(i)?"pascal":function(e){return"string"==typeof e&&o.test(e)&&!s.test(e)&&(e.includes("-")||c.test(e))}(i)?"kebab":null}function j(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)||i.test(e))return{string:e,shouldTransform:!1,sourceCase:null,singleWord:!1};const t=_(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?A[o][t](n):n:n}const $=new Map;function O(e,t){var n;if(e.length>100)return j(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:j(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,c=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,c)){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?j(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:j(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,c)){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(c===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 L(e,t){var n;const r=(null==t?void 0:t.targetCase)||"camel",o=null!==(n=null==t?void 0:t.depth)&&void 0!==n?n:1/0;if(s=r,!M.includes(s))throw new TypeError(`Unsupported targetCase ${f(r)}. Use one of: ${M.join(", ")}.`);var s;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:r,depth:o,exclude:null},a=null==t?void 0:t.exclude,c=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===c?x(e,u):x(e,c.options,c.visitedRefs)}export{L as transform};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deep-case-crafter",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, deeply (objects, arrays, Map, Set), with exact TypeScript types",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -61,7 +61,7 @@
61
61
  "test-coverage": "npm test -- --coverage",
62
62
  "test:types": "tsc --noEmit",
63
63
  "tsc": "tsc --noEmit",
64
- "test:dist-types": "tsc -p dist-type-tests && tsc -p dist-type-tests/tsconfig.declarations.json && tsc -p dist-type-tests/tsconfig.loose.json && tsc -p dist-type-tests/tsconfig.exact.json",
64
+ "test:dist-types": "tsc -p dist-type-tests && tsc -p dist-type-tests/tsconfig.declarations.json && node dist-type-tests/checkDeclarations.mjs && tsc -p dist-type-tests/tsconfig.consumer.json && tsc -p dist-type-tests/tsconfig.loose.json && tsc -p dist-type-tests/tsconfig.exact.json",
65
65
  "lint": "eslint --ext .js,.jsx,.ts,.tsx src/ --max-warnings=0 --no-cache",
66
66
  "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx src/ --fix --max-warnings=0 --no-cache",
67
67
  "format": "prettier --write src dist-type-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
package/readme.md CHANGED
@@ -39,6 +39,9 @@ const body = transform(user, { targetCase: 'snake' });
39
39
  returned untouched.
40
40
  - **Keys that are data keep their names**: UUIDs always, and any other key whose path
41
41
  you list in [`exclude`](#leaving-keys-unchanged), such as `'users_by_id.*'`.
42
+ - **Single keys too**: [`transformKey`](#transformkeykey-options) converts one key by
43
+ the same rules, `transformKey('userID', { targetCase: 'snake' })` → `'user_id'`, typed
44
+ `'user_id'`.
42
45
  - No dependencies, with ES module and CommonJS builds.
43
46
 
44
47
  ## Installation
@@ -162,6 +165,24 @@ console.log(transform(strings, { targetCase: 'camel' }));
162
165
 
163
166
  ---
164
167
 
168
+ ### 7. Convert a single key with `transformKey`
169
+
170
+ ```typescript
171
+ import { transformKey } from 'deep-case-crafter';
172
+
173
+ transformKey('user_id'); // 'userId'
174
+ transformKey('userID', { targetCase: 'snake' }); // 'user_id'
175
+
176
+ type SortField = 'createdAt' | 'userID';
177
+
178
+ function toSortParam(field: SortField) {
179
+ return transformKey(field, { targetCase: 'snake' });
180
+ // ^? 'created_at' | 'user_id'
181
+ }
182
+ ```
183
+
184
+ ---
185
+
165
186
  ## How it compares
166
187
 
167
188
  Checked in October 2026 against
@@ -191,8 +212,11 @@ Where the others are better or different:
191
212
  and `objectToSnake` take about 1.1 KB gzipped, deep-case-crafter's `transform` about
192
213
  2.6 KB.
193
214
  - **More output**: ts-case-convert and es-toolkit also convert to SCREAMING_SNAKE_CASE,
194
- and both have functions for single strings: `toSnake('userId')` and
195
- `snakeCase('userId')` return `'user_id'`. deep-case-crafter has neither.
215
+ which deep-case-crafter does not.
216
+ - **Single strings**: like `transformKey`, ts-case-convert's `toSnake('userId')` and
217
+ es-toolkit's `snakeCase('userId')` return `'user_id'`. es-toolkit's functions also
218
+ convert text, such as `'Hello World'` → `'hello_world'`, which `transformKey` returns
219
+ unchanged.
196
220
  - **Digits**: ts-case-convert separates digits by default (`addressLine1` →
197
221
  `address_line_1`), so `address_line_1` → `addressLine1` → `address_line_1`
198
222
  round-trips. Its `ts-case-convert/no-split-numbers` entry point keeps digits attached
@@ -358,6 +382,33 @@ type of the `exclude` paths, such as `['metadata.*']`.
358
382
  untouched when it has a `Symbol.toStringTag` (as every class implementing `Map` or
359
383
  `Set` does), and rebuilt as a plain object otherwise.
360
384
 
385
+ ### `transformKey(key, options)`
386
+
387
+ Converts a single key by the same rules as the keys of `transform`'s result.
388
+
389
+ - **key**: The key to transform, a string. Anything else throws a `TypeError`.
390
+ - **options**:
391
+ - `targetCase` (optional): as for `transform` (default: `'camel'`). Pass it in an
392
+ object: `transformKey(key, 'snake')` throws a `TypeError`.
393
+
394
+ ```typescript
395
+ transformKey('HTTPResponse', { targetCase: 'snake' }); // 'http_response'
396
+ transformKey('Hello World', { targetCase: 'snake' }); // 'Hello World'
397
+ ```
398
+
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)).
402
+
403
+ The result type is the converted key. A string literal gives a literal, a union of keys
404
+ or of target cases gives the union of their results, and a `string` gives `string`.
405
+ `TransformKey<Key, TargetCase>` names it: `TransformKey<'userID', 'snake'>` is
406
+ `'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.
408
+
409
+ To use it in a callback, wrap it in a function:
410
+ `keys.map((key) => transformKey(key, { targetCase: 'snake' }))`.
411
+
361
412
  ---
362
413
 
363
414
  ## Advanced: explicit output type overload
@@ -399,9 +450,9 @@ Trailing digits are kept but treated as part of the word, so they do **not** by
399
450
  themselves preserve a key — `address_1` becomes `address1`, and the single word
400
451
  `item2` stays `item2`.
401
452
 
402
- These rules apply to `Map` keys and to the result types as well. To keep other keys
403
- unchanged, such as IDs or the keys of free-form metadata, list their paths in
404
- [`exclude`](#leaving-keys-unchanged).
453
+ These rules apply to `Map` keys, to `transformKey` and to the result types as well. To
454
+ keep other keys unchanged, such as IDs or the keys of free-form metadata, list their
455
+ paths in [`exclude`](#leaving-keys-unchanged).
405
456
 
406
457
  ---
407
458
 
@@ -442,8 +493,8 @@ deep-case-crafter follows [semantic versioning](https://semver.org). The v3 API
442
493
  stable. Key outputs change only to fix bugs, and every change is listed in the
443
494
  [changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md): if you
444
495
  store converted keys, such as database columns, check it before upgrading. A minor
445
- version can add an option to `TransformOptions`, which `Required<TransformOptions>` then
446
- requires.
496
+ version can add an option, which `Required<TransformOptions>` and
497
+ `Required<TransformKeyOptions>` then require.
447
498
 
448
499
  ### Upgrading to v3
449
500