deep-case-crafter 3.4.1 → 3.5.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
@@ -84,9 +84,10 @@ interface TransformOptions extends TransformKeyOptions {
84
84
  * Use it only for tree-shaped data. An object is then copied once for every path that
85
85
  * leads to it, so shared objects inside shared objects multiply. Data with more than
86
86
  * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
- * have, is converted again from the start with the bookkeeping, so it takes longer and
88
- * getters can run twice. With a finite `depth`, circular references stop at the depth
89
- * limit before that, and are copied once per path down to it.
87
+ * have, is converted again from the start with the bookkeeping. That takes longer and
88
+ * runs its getters again; a getter on a circular path has by then run up to 256 times.
89
+ * With a finite `depth`, a circular reference stops at the depth limit instead when that
90
+ * comes first, and is copied once per path down to it.
90
91
  *
91
92
  * @default true
92
93
  */
@@ -108,10 +109,10 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
108
109
  };
109
110
  /**
110
111
  * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
111
- * The options parameter is then `TransformKeyOptions` itself rather than an
112
- * intersection, so TypeScript checks the argument for a property in common with it, as
113
- * it does for every type whose properties are all optional: `'snake'`, an array, or a
114
- * stored options object with only a misspelled option is rejected.
112
+ * The options parameter is then `TransformKeyOptions` itself, not an intersection with a
113
+ * type that has no properties, so TypeScript checks the argument for a property in
114
+ * common with it, as it does for every type whose properties are all optional: `'snake'`,
115
+ * an array, or a stored options object with only a misspelled option is rejected.
115
116
  */
116
117
  type NoUnknownKeyOptions<Options> = [
117
118
  Exclude<keyof Options, keyof TransformKeyOptions>
@@ -218,9 +219,11 @@ type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern ex
218
219
  * Keys the runtime leaves unchanged (`transformCase`): keys containing a
219
220
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
220
221
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
221
- * keys that the other rules do not preserve are compared with the UUID form.
222
+ * keys that the other rules do not preserve are compared with the UUID form, and only
223
+ * those with at least four hyphens, as a UUID has: most comparisons stop at the first or
224
+ * second character, but running one for every key adds up.
222
225
  */
223
- type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
226
+ type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : K extends `${string}-${string}-${string}-${string}-${string}` ? IsUuid<K> : false : true;
224
227
  /**
225
228
  * The case the runtime detects for a key that is not preserved (`transformCase`), or
226
229
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
@@ -312,6 +315,16 @@ type IsDepthReached<Depth extends number | undefined, Level extends unknown[]> =
312
315
  type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
313
316
  Depth
314
317
  ] extends [undefined] ? Level : [...Level, unknown];
318
+ /**
319
+ * A value at the nesting level `Level`: `T` itself once `Depth` is reached, and
320
+ * `TransformValue` below it. Checked before `TransformValue` distributes over `T`:
321
+ * generic code can then assign a `T` to `TransformResult<T, 'camel', 0>`, and to the
322
+ * values of a Map or Set at the depth limit. The properties of an object are checked
323
+ * once for the whole object instead (see `TransformValue`). `TransformValue` keeps its
324
+ * own check, which declaration files emitted with earlier versions rely on
325
+ * (`TransformValue<T, "camel", 1, [unknown]>`).
326
+ */
327
+ type TransformValueAt<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsDepthReached<Depth, Level> extends true ? T : TransformValue<T, To, Depth, Level>;
315
328
  /**
316
329
  * Transforms `T` the way `internalTransform` does: arrays keep their nesting level,
317
330
  * while the values of objects, Maps and Sets are one level deeper.
@@ -319,13 +332,28 @@ type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
319
332
  * Arrays are typed as arrays of transformed items, which lets TypeScript defer
320
333
  * recursive types such as a JSON value type; only tuples are mapped position by position.
321
334
  *
335
+ * Its first check, `T extends Primitive`, distributes over `T`, so that TypeScript can
336
+ * resolve it through the constraint of a type parameter: `transform(user).userId`
337
+ * compiles in `<T extends { user_id: number }>(user: T) => ...`. `any` takes both
338
+ * branches, which gives `any`. TypeScript before 5.0 no longer sees `T` itself in the
339
+ * result, so it widens a literal argument: `transform('id')` is `string` there.
340
+ * `T extends object ? ... : T` would keep it, but `T` then becomes `T & object` in the
341
+ * branch that transforms it, which makes large types up to 70% slower to check.
342
+ *
343
+ * Where the depth limit leaves values as they are, it is checked before this type is
344
+ * entered, so that a `T` can still be assigned there: once for all the properties of an
345
+ * object (the costliest place to check it per value), and in `TransformValueAt`
346
+ * elsewhere.
347
+ *
322
348
  * Use `TransformResult` instead. This type is exported only so that declaration files
323
349
  * can refer to it: the result of a generic wrapper around `transform` is written in terms
324
350
  * of it, and an unexported recursive type would have to be expanded there without end.
325
351
  */
326
- type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
352
+ type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = T extends Primitive ? T : IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
327
353
  [I in keyof T]: TransformValue<T[I], To, Depth, Level>;
328
- } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? {
354
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? IsDepthReached<Depth, NextLevel<Depth, Level>> extends true ? {
355
+ [K in keyof T as K extends string ? TransformKey<K, To> : K]: T[K];
356
+ } : {
329
357
  [K in keyof T as K extends string ? TransformKey<K, To> : K]: K extends symbol ? T[K] : TransformValue<T[K], To, Depth, NextLevel<Depth, Level>>;
330
358
  } : T;
331
359
  /**
@@ -333,24 +361,31 @@ type TransformValue<T, To extends StringCase, Depth extends number | undefined,
333
361
  * below this level (see `excludeTypes.ts`). A key at which a path ends keeps its name,
334
362
  * and the paths that continue below a key go on into its value. Arrays and Sets pass
335
363
  * them on to their items, which add no key to a path. Values that no path reaches are
336
- * transformed by `TransformValue`.
364
+ * transformed by `TransformValue`. Like `TransformValue`, it distributes over `T`, but
365
+ * with a check of its own (`T extends unknown`): with `TransformValue`'s first check,
366
+ * TypeScript 4.7 reads the inline paths of a call whose result is annotated, as in
367
+ * `(x: T): TransformResult<T, 'camel', undefined, ['a.*']> => transform(x, { exclude:
368
+ * ['a.*'] })`, as a `string[]`, and the call's result as `unknown`.
337
369
  *
338
370
  * Use `TransformResult` instead. This type is exported for the same reason as
339
371
  * `TransformValue`.
340
372
  */
341
- type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
373
+ type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = T extends unknown ? IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
342
374
  [I in keyof T]: TransformValueExcluding<T[I], To, Depth, Level, Paths>;
343
- } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformValueExcluding<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
375
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformBelow<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
344
376
  [K in keyof T as ExcludingKey<K, To, PathEnds<Paths>>]: K extends symbol ? T[K] : TransformBelowKey<T[K], To, Depth, NextLevel<Depth, Level>, Paths, KeyName<K & (string | number)>>;
345
- } : T;
377
+ } : T : never;
346
378
  /**
347
379
  * A key of an object, or a member of a Map's key type: it keeps its name where a path
348
380
  * of `Ends` ends at it, and is transformed otherwise. A key type that is not a single
349
381
  * literal keeps its name only where `*` ends.
350
382
  */
351
383
  type ExcludingKey<K, To extends StringCase, Ends extends string> = K extends string ? '*' extends Ends ? K : IsStringLiteral<K> extends true ? [KeyName<K> & Ends] extends [never] ? TransformKey<K, To> : K : TransformKey<K, To> : K;
352
- /** `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left */
353
- type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
384
+ /**
385
+ * `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left,
386
+ * and `T` itself at the depth limit (see `TransformValueAt`)
387
+ */
388
+ type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsDepthReached<Depth, Level> extends true ? T : [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
354
389
  /**
355
390
  * The value `T` under the key `Key`. A key type that is not a single literal may stand
356
391
  * for several keys, which the paths may treat differently: each gives its own result.
@@ -373,7 +408,7 @@ type TransformBelowMapKeys<V, To extends StringCase, Depth extends number | unde
373
408
  * Applies a numeric literal `Depth`: a negative depth transforms nothing, and a
374
409
  * fractional depth is not supported precisely, so its result is `unknown`.
375
410
  */
376
- type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValue<T, To, Depth, []>;
411
+ type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueAt<T, To, Depth, []>;
377
412
  /**
378
413
  * `true` for a numeric literal, branded or not (`2`, `2 & Brand`), and `false` for
379
414
  * `number` and a branded `number`: a literal shares no value with at least one of two
@@ -389,7 +424,7 @@ type TransformToDepth<T, To extends StringCase, Depth extends number | undefined
389
424
  * `TransformToDepth` (and `TransformWithDepth`) for the `exclude` paths `Paths`. Kept
390
425
  * apart, so that results without `exclude` take no extra step.
391
426
  */
392
- type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueExcluding<T, To, Depth & number, [], Paths> : unknown : never;
427
+ type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformBelow<T, To, Depth & number, [], Paths> : unknown : never;
393
428
  /**
394
429
  * The result for an `exclude` tuple, collecting its paths into `Paths`. A union element
395
430
  * stands for several tuples, which give their own results. A path with an empty key makes
@@ -513,7 +548,11 @@ declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
513
548
  * strings is still accepted, and so is `undefined`: with `exactOptionalPropertyTypes`, an
514
549
  * optional property only accepts it when its type includes it. The parameter type does
515
550
  * not mention `exclude` itself: intersecting the captured tuple with these types would
516
- * make every call compare array types member by member.
551
+ * make every call compare array types member by member. `object` rejects a primitive
552
+ * passed as the options, such as the target case itself in `transform(data, 'snake')`.
553
+ * Without it, such a value compiles: `Options` falls back to its constraint, whose
554
+ * `NoUnknownOptions` has no properties, and TypeScript only checks a value for a property
555
+ * in common with an intersection when every member has properties, all optional.
517
556
  *
518
557
  * The type parameter defaults only apply when type arguments are passed explicitly, and
519
558
  * give an `unknown` result type. An explicit `any` as the second type argument was the
@@ -525,7 +564,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
525
564
  } = TransformOptions, Paths extends readonly string[] = readonly string[]>(data: T, options: Options & {
526
565
  targetCase?: To;
527
566
  depth?: Depth;
528
- } & NoUnknownOptions<Options>): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
567
+ } & NoUnknownOptions<Options> & object): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
529
568
  /**
530
569
  * The 2.x form with explicit `<Data, SourceCase, TargetCase>` type arguments.
531
570
  *
package/dist/index.d.mts CHANGED
@@ -84,9 +84,10 @@ interface TransformOptions extends TransformKeyOptions {
84
84
  * Use it only for tree-shaped data. An object is then copied once for every path that
85
85
  * leads to it, so shared objects inside shared objects multiply. Data with more than
86
86
  * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
- * have, is converted again from the start with the bookkeeping, so it takes longer and
88
- * getters can run twice. With a finite `depth`, circular references stop at the depth
89
- * limit before that, and are copied once per path down to it.
87
+ * have, is converted again from the start with the bookkeeping. That takes longer and
88
+ * runs its getters again; a getter on a circular path has by then run up to 256 times.
89
+ * With a finite `depth`, a circular reference stops at the depth limit instead when that
90
+ * comes first, and is copied once per path down to it.
90
91
  *
91
92
  * @default true
92
93
  */
@@ -108,10 +109,10 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
108
109
  };
109
110
  /**
110
111
  * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
111
- * The options parameter is then `TransformKeyOptions` itself rather than an
112
- * intersection, so TypeScript checks the argument for a property in common with it, as
113
- * it does for every type whose properties are all optional: `'snake'`, an array, or a
114
- * stored options object with only a misspelled option is rejected.
112
+ * The options parameter is then `TransformKeyOptions` itself, not an intersection with a
113
+ * type that has no properties, so TypeScript checks the argument for a property in
114
+ * common with it, as it does for every type whose properties are all optional: `'snake'`,
115
+ * an array, or a stored options object with only a misspelled option is rejected.
115
116
  */
116
117
  type NoUnknownKeyOptions<Options> = [
117
118
  Exclude<keyof Options, keyof TransformKeyOptions>
@@ -218,9 +219,11 @@ type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern ex
218
219
  * Keys the runtime leaves unchanged (`transformCase`): keys containing a
219
220
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
220
221
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
221
- * keys that the other rules do not preserve are compared with the UUID form.
222
+ * keys that the other rules do not preserve are compared with the UUID form, and only
223
+ * those with at least four hyphens, as a UUID has: most comparisons stop at the first or
224
+ * second character, but running one for every key adds up.
222
225
  */
223
- type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
226
+ type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : K extends `${string}-${string}-${string}-${string}-${string}` ? IsUuid<K> : false : true;
224
227
  /**
225
228
  * The case the runtime detects for a key that is not preserved (`transformCase`), or
226
229
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
@@ -312,6 +315,16 @@ type IsDepthReached<Depth extends number | undefined, Level extends unknown[]> =
312
315
  type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
313
316
  Depth
314
317
  ] extends [undefined] ? Level : [...Level, unknown];
318
+ /**
319
+ * A value at the nesting level `Level`: `T` itself once `Depth` is reached, and
320
+ * `TransformValue` below it. Checked before `TransformValue` distributes over `T`:
321
+ * generic code can then assign a `T` to `TransformResult<T, 'camel', 0>`, and to the
322
+ * values of a Map or Set at the depth limit. The properties of an object are checked
323
+ * once for the whole object instead (see `TransformValue`). `TransformValue` keeps its
324
+ * own check, which declaration files emitted with earlier versions rely on
325
+ * (`TransformValue<T, "camel", 1, [unknown]>`).
326
+ */
327
+ type TransformValueAt<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsDepthReached<Depth, Level> extends true ? T : TransformValue<T, To, Depth, Level>;
315
328
  /**
316
329
  * Transforms `T` the way `internalTransform` does: arrays keep their nesting level,
317
330
  * while the values of objects, Maps and Sets are one level deeper.
@@ -319,13 +332,28 @@ type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
319
332
  * Arrays are typed as arrays of transformed items, which lets TypeScript defer
320
333
  * recursive types such as a JSON value type; only tuples are mapped position by position.
321
334
  *
335
+ * Its first check, `T extends Primitive`, distributes over `T`, so that TypeScript can
336
+ * resolve it through the constraint of a type parameter: `transform(user).userId`
337
+ * compiles in `<T extends { user_id: number }>(user: T) => ...`. `any` takes both
338
+ * branches, which gives `any`. TypeScript before 5.0 no longer sees `T` itself in the
339
+ * result, so it widens a literal argument: `transform('id')` is `string` there.
340
+ * `T extends object ? ... : T` would keep it, but `T` then becomes `T & object` in the
341
+ * branch that transforms it, which makes large types up to 70% slower to check.
342
+ *
343
+ * Where the depth limit leaves values as they are, it is checked before this type is
344
+ * entered, so that a `T` can still be assigned there: once for all the properties of an
345
+ * object (the costliest place to check it per value), and in `TransformValueAt`
346
+ * elsewhere.
347
+ *
322
348
  * Use `TransformResult` instead. This type is exported only so that declaration files
323
349
  * can refer to it: the result of a generic wrapper around `transform` is written in terms
324
350
  * of it, and an unexported recursive type would have to be expanded there without end.
325
351
  */
326
- type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
352
+ type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = T extends Primitive ? T : IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
327
353
  [I in keyof T]: TransformValue<T[I], To, Depth, Level>;
328
- } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? {
354
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? IsDepthReached<Depth, NextLevel<Depth, Level>> extends true ? {
355
+ [K in keyof T as K extends string ? TransformKey<K, To> : K]: T[K];
356
+ } : {
329
357
  [K in keyof T as K extends string ? TransformKey<K, To> : K]: K extends symbol ? T[K] : TransformValue<T[K], To, Depth, NextLevel<Depth, Level>>;
330
358
  } : T;
331
359
  /**
@@ -333,24 +361,31 @@ type TransformValue<T, To extends StringCase, Depth extends number | undefined,
333
361
  * below this level (see `excludeTypes.ts`). A key at which a path ends keeps its name,
334
362
  * and the paths that continue below a key go on into its value. Arrays and Sets pass
335
363
  * them on to their items, which add no key to a path. Values that no path reaches are
336
- * transformed by `TransformValue`.
364
+ * transformed by `TransformValue`. Like `TransformValue`, it distributes over `T`, but
365
+ * with a check of its own (`T extends unknown`): with `TransformValue`'s first check,
366
+ * TypeScript 4.7 reads the inline paths of a call whose result is annotated, as in
367
+ * `(x: T): TransformResult<T, 'camel', undefined, ['a.*']> => transform(x, { exclude:
368
+ * ['a.*'] })`, as a `string[]`, and the call's result as `unknown`.
337
369
  *
338
370
  * Use `TransformResult` instead. This type is exported for the same reason as
339
371
  * `TransformValue`.
340
372
  */
341
- type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
373
+ type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = T extends unknown ? IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
342
374
  [I in keyof T]: TransformValueExcluding<T[I], To, Depth, Level, Paths>;
343
- } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformValueExcluding<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
375
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformBelow<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
344
376
  [K in keyof T as ExcludingKey<K, To, PathEnds<Paths>>]: K extends symbol ? T[K] : TransformBelowKey<T[K], To, Depth, NextLevel<Depth, Level>, Paths, KeyName<K & (string | number)>>;
345
- } : T;
377
+ } : T : never;
346
378
  /**
347
379
  * A key of an object, or a member of a Map's key type: it keeps its name where a path
348
380
  * of `Ends` ends at it, and is transformed otherwise. A key type that is not a single
349
381
  * literal keeps its name only where `*` ends.
350
382
  */
351
383
  type ExcludingKey<K, To extends StringCase, Ends extends string> = K extends string ? '*' extends Ends ? K : IsStringLiteral<K> extends true ? [KeyName<K> & Ends] extends [never] ? TransformKey<K, To> : K : TransformKey<K, To> : K;
352
- /** `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left */
353
- type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
384
+ /**
385
+ * `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left,
386
+ * and `T` itself at the depth limit (see `TransformValueAt`)
387
+ */
388
+ type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsDepthReached<Depth, Level> extends true ? T : [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
354
389
  /**
355
390
  * The value `T` under the key `Key`. A key type that is not a single literal may stand
356
391
  * for several keys, which the paths may treat differently: each gives its own result.
@@ -373,7 +408,7 @@ type TransformBelowMapKeys<V, To extends StringCase, Depth extends number | unde
373
408
  * Applies a numeric literal `Depth`: a negative depth transforms nothing, and a
374
409
  * fractional depth is not supported precisely, so its result is `unknown`.
375
410
  */
376
- type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValue<T, To, Depth, []>;
411
+ type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueAt<T, To, Depth, []>;
377
412
  /**
378
413
  * `true` for a numeric literal, branded or not (`2`, `2 & Brand`), and `false` for
379
414
  * `number` and a branded `number`: a literal shares no value with at least one of two
@@ -389,7 +424,7 @@ type TransformToDepth<T, To extends StringCase, Depth extends number | undefined
389
424
  * `TransformToDepth` (and `TransformWithDepth`) for the `exclude` paths `Paths`. Kept
390
425
  * apart, so that results without `exclude` take no extra step.
391
426
  */
392
- type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueExcluding<T, To, Depth & number, [], Paths> : unknown : never;
427
+ type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformBelow<T, To, Depth & number, [], Paths> : unknown : never;
393
428
  /**
394
429
  * The result for an `exclude` tuple, collecting its paths into `Paths`. A union element
395
430
  * stands for several tuples, which give their own results. A path with an empty key makes
@@ -513,7 +548,11 @@ declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
513
548
  * strings is still accepted, and so is `undefined`: with `exactOptionalPropertyTypes`, an
514
549
  * optional property only accepts it when its type includes it. The parameter type does
515
550
  * not mention `exclude` itself: intersecting the captured tuple with these types would
516
- * make every call compare array types member by member.
551
+ * make every call compare array types member by member. `object` rejects a primitive
552
+ * passed as the options, such as the target case itself in `transform(data, 'snake')`.
553
+ * Without it, such a value compiles: `Options` falls back to its constraint, whose
554
+ * `NoUnknownOptions` has no properties, and TypeScript only checks a value for a property
555
+ * in common with an intersection when every member has properties, all optional.
517
556
  *
518
557
  * The type parameter defaults only apply when type arguments are passed explicitly, and
519
558
  * give an `unknown` result type. An explicit `any` as the second type argument was the
@@ -525,7 +564,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
525
564
  } = TransformOptions, Paths extends readonly string[] = readonly string[]>(data: T, options: Options & {
526
565
  targetCase?: To;
527
566
  depth?: Depth;
528
- } & NoUnknownOptions<Options>): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
567
+ } & NoUnknownOptions<Options> & object): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
529
568
  /**
530
569
  * The 2.x form with explicit `<Data, SourceCase, TargetCase>` type arguments.
531
570
  *
package/dist/index.d.ts CHANGED
@@ -84,9 +84,10 @@ interface TransformOptions extends TransformKeyOptions {
84
84
  * Use it only for tree-shaped data. An object is then copied once for every path that
85
85
  * leads to it, so shared objects inside shared objects multiply. Data with more than
86
86
  * 256 nested objects, arrays, Maps and Sets on one path, as circular references always
87
- * have, is converted again from the start with the bookkeeping, so it takes longer and
88
- * getters can run twice. With a finite `depth`, circular references stop at the depth
89
- * limit before that, and are copied once per path down to it.
87
+ * have, is converted again from the start with the bookkeeping. That takes longer and
88
+ * runs its getters again; a getter on a circular path has by then run up to 256 times.
89
+ * With a finite `depth`, a circular reference stops at the depth limit instead when that
90
+ * comes first, and is copied once per path down to it.
90
91
  *
91
92
  * @default true
92
93
  */
@@ -108,10 +109,10 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
108
109
  };
109
110
  /**
110
111
  * `NoUnknownOptions` for `transformKey`, but `unknown` when every option name is known.
111
- * The options parameter is then `TransformKeyOptions` itself rather than an
112
- * intersection, so TypeScript checks the argument for a property in common with it, as
113
- * it does for every type whose properties are all optional: `'snake'`, an array, or a
114
- * stored options object with only a misspelled option is rejected.
112
+ * The options parameter is then `TransformKeyOptions` itself, not an intersection with a
113
+ * type that has no properties, so TypeScript checks the argument for a property in
114
+ * common with it, as it does for every type whose properties are all optional: `'snake'`,
115
+ * an array, or a stored options object with only a misspelled option is rejected.
115
116
  */
116
117
  type NoUnknownKeyOptions<Options> = [
117
118
  Exclude<keyof Options, keyof TransformKeyOptions>
@@ -218,9 +219,11 @@ type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern ex
218
219
  * Keys the runtime leaves unchanged (`transformCase`): keys containing a
219
220
  * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
220
221
  * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
221
- * keys that the other rules do not preserve are compared with the UUID form.
222
+ * keys that the other rules do not preserve are compared with the UUID form, and only
223
+ * those with at least four hyphens, as a UUID has: most comparisons stop at the first or
224
+ * second character, but running one for every key adds up.
222
225
  */
223
- type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
226
+ type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : K extends `${string}-${string}-${string}-${string}-${string}` ? IsUuid<K> : false : true;
224
227
  /**
225
228
  * The case the runtime detects for a key that is not preserved (`transformCase`), or
226
229
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
@@ -312,6 +315,16 @@ type IsDepthReached<Depth extends number | undefined, Level extends unknown[]> =
312
315
  type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
313
316
  Depth
314
317
  ] extends [undefined] ? Level : [...Level, unknown];
318
+ /**
319
+ * A value at the nesting level `Level`: `T` itself once `Depth` is reached, and
320
+ * `TransformValue` below it. Checked before `TransformValue` distributes over `T`:
321
+ * generic code can then assign a `T` to `TransformResult<T, 'camel', 0>`, and to the
322
+ * values of a Map or Set at the depth limit. The properties of an object are checked
323
+ * once for the whole object instead (see `TransformValue`). `TransformValue` keeps its
324
+ * own check, which declaration files emitted with earlier versions rely on
325
+ * (`TransformValue<T, "camel", 1, [unknown]>`).
326
+ */
327
+ type TransformValueAt<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsDepthReached<Depth, Level> extends true ? T : TransformValue<T, To, Depth, Level>;
315
328
  /**
316
329
  * Transforms `T` the way `internalTransform` does: arrays keep their nesting level,
317
330
  * while the values of objects, Maps and Sets are one level deeper.
@@ -319,13 +332,28 @@ type NextLevel<Depth extends number | undefined, Level extends unknown[]> = [
319
332
  * Arrays are typed as arrays of transformed items, which lets TypeScript defer
320
333
  * recursive types such as a JSON value type; only tuples are mapped position by position.
321
334
  *
335
+ * Its first check, `T extends Primitive`, distributes over `T`, so that TypeScript can
336
+ * resolve it through the constraint of a type parameter: `transform(user).userId`
337
+ * compiles in `<T extends { user_id: number }>(user: T) => ...`. `any` takes both
338
+ * branches, which gives `any`. TypeScript before 5.0 no longer sees `T` itself in the
339
+ * result, so it widens a literal argument: `transform('id')` is `string` there.
340
+ * `T extends object ? ... : T` would keep it, but `T` then becomes `T & object` in the
341
+ * branch that transforms it, which makes large types up to 70% slower to check.
342
+ *
343
+ * Where the depth limit leaves values as they are, it is checked before this type is
344
+ * entered, so that a `T` can still be assigned there: once for all the properties of an
345
+ * object (the costliest place to check it per value), and in `TransformValueAt`
346
+ * elsewhere.
347
+ *
322
348
  * Use `TransformResult` instead. This type is exported only so that declaration files
323
349
  * can refer to it: the result of a generic wrapper around `transform` is written in terms
324
350
  * of it, and an unexported recursive type would have to be expanded there without end.
325
351
  */
326
- type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
352
+ type TransformValue<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[]> = T extends Primitive ? T : IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValue<Item, To, Depth, Level>[] : readonly TransformValue<Item, To, Depth, Level>[] : {
327
353
  [I in keyof T]: TransformValue<T[I], To, Depth, Level>;
328
- } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValue<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? {
354
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<K extends string ? TransformKey<K, To> : K, TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends ReadonlySet<infer V> ? Set<TransformValueAt<V, To, Depth, NextLevel<Depth, Level>>> : T extends UntouchedValue ? T : T extends object ? IsDepthReached<Depth, NextLevel<Depth, Level>> extends true ? {
355
+ [K in keyof T as K extends string ? TransformKey<K, To> : K]: T[K];
356
+ } : {
329
357
  [K in keyof T as K extends string ? TransformKey<K, To> : K]: K extends symbol ? T[K] : TransformValue<T[K], To, Depth, NextLevel<Depth, Level>>;
330
358
  } : T;
331
359
  /**
@@ -333,24 +361,31 @@ type TransformValue<T, To extends StringCase, Depth extends number | undefined,
333
361
  * below this level (see `excludeTypes.ts`). A key at which a path ends keeps its name,
334
362
  * and the paths that continue below a key go on into its value. Arrays and Sets pass
335
363
  * them on to their items, which add no key to a path. Values that no path reaches are
336
- * transformed by `TransformValue`.
364
+ * transformed by `TransformValue`. Like `TransformValue`, it distributes over `T`, but
365
+ * with a check of its own (`T extends unknown`): with `TransformValue`'s first check,
366
+ * TypeScript 4.7 reads the inline paths of a call whose result is annotated, as in
367
+ * `(x: T): TransformResult<T, 'camel', undefined, ['a.*']> => transform(x, { exclude:
368
+ * ['a.*'] })`, as a `string[]`, and the call's result as `unknown`.
337
369
  *
338
370
  * Use `TransformResult` instead. This type is exported for the same reason as
339
371
  * `TransformValue`.
340
372
  */
341
- type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
373
+ type TransformValueExcluding<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = T extends unknown ? IsAny<T> extends true ? T : IsDepthReached<Depth, Level> extends true ? T : T extends Primitive ? T : T extends readonly (infer Item)[] ? number extends T['length'] ? T extends unknown[] ? TransformValueExcluding<Item, To, Depth, Level, Paths>[] : readonly TransformValueExcluding<Item, To, Depth, Level, Paths>[] : {
342
374
  [I in keyof T]: TransformValueExcluding<T[I], To, Depth, Level, Paths>;
343
- } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformValueExcluding<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
375
+ } : T extends ReadonlyMap<infer K, infer V> ? Map<ExcludingKey<K, To, PathEnds<Paths>>, TransformBelowMapKeys<V, To, Depth, NextLevel<Depth, Level>, MapPathsBelow<K, Paths>>> : T extends ReadonlySet<infer V> ? Set<TransformBelow<V, To, Depth, NextLevel<Depth, Level>, Paths>> : T extends UntouchedValue ? T : T extends object ? {
344
376
  [K in keyof T as ExcludingKey<K, To, PathEnds<Paths>>]: K extends symbol ? T[K] : TransformBelowKey<T[K], To, Depth, NextLevel<Depth, Level>, Paths, KeyName<K & (string | number)>>;
345
- } : T;
377
+ } : T : never;
346
378
  /**
347
379
  * A key of an object, or a member of a Map's key type: it keeps its name where a path
348
380
  * of `Ends` ends at it, and is transformed otherwise. A key type that is not a single
349
381
  * literal keeps its name only where `*` ends.
350
382
  */
351
383
  type ExcludingKey<K, To extends StringCase, Ends extends string> = K extends string ? '*' extends Ends ? K : IsStringLiteral<K> extends true ? [KeyName<K> & Ends] extends [never] ? TransformKey<K, To> : K : TransformKey<K, To> : K;
352
- /** `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left */
353
- type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
384
+ /**
385
+ * `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left,
386
+ * and `T` itself at the depth limit (see `TransformValueAt`)
387
+ */
388
+ type TransformBelow<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string> = IsDepthReached<Depth, Level> extends true ? T : [Paths] extends [never] ? TransformValue<T, To, Depth, Level> : TransformValueExcluding<T, To, Depth, Level, Paths>;
354
389
  /**
355
390
  * The value `T` under the key `Key`. A key type that is not a single literal may stand
356
391
  * for several keys, which the paths may treat differently: each gives its own result.
@@ -373,7 +408,7 @@ type TransformBelowMapKeys<V, To extends StringCase, Depth extends number | unde
373
408
  * Applies a numeric literal `Depth`: a negative depth transforms nothing, and a
374
409
  * fractional depth is not supported precisely, so its result is `unknown`.
375
410
  */
376
- type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValue<T, To, Depth, []>;
411
+ type TransformWithDepth<T, To extends StringCase, Depth extends number> = `${Depth}` extends `-${string}` ? T : `${Depth}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueAt<T, To, Depth, []>;
377
412
  /**
378
413
  * `true` for a numeric literal, branded or not (`2`, `2 & Brand`), and `false` for
379
414
  * `number` and a branded `number`: a literal shares no value with at least one of two
@@ -389,7 +424,7 @@ type TransformToDepth<T, To extends StringCase, Depth extends number | undefined
389
424
  * `TransformToDepth` (and `TransformWithDepth`) for the `exclude` paths `Paths`. Kept
390
425
  * apart, so that results without `exclude` take no extra step.
391
426
  */
392
- type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformValueExcluding<T, To, Depth & number, [], Paths> : unknown : never;
427
+ type TransformToDepthExcluding<T, To extends StringCase, Depth extends number | undefined, Paths extends string> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValueExcluding<T, To, undefined, [], Paths> : IsNumberLiteral<Depth & number> extends true ? `${Depth & number}` extends `-${string}` ? T : `${Depth & number}` extends `${string}${'.' | 'e'}${string}` ? unknown : TransformBelow<T, To, Depth & number, [], Paths> : unknown : never;
393
428
  /**
394
429
  * The result for an `exclude` tuple, collecting its paths into `Paths`. A union element
395
430
  * stands for several tuples, which give their own results. A path with an empty key makes
@@ -513,7 +548,11 @@ declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
513
548
  * strings is still accepted, and so is `undefined`: with `exactOptionalPropertyTypes`, an
514
549
  * optional property only accepts it when its type includes it. The parameter type does
515
550
  * not mention `exclude` itself: intersecting the captured tuple with these types would
516
- * make every call compare array types member by member.
551
+ * make every call compare array types member by member. `object` rejects a primitive
552
+ * passed as the options, such as the target case itself in `transform(data, 'snake')`.
553
+ * Without it, such a value compiles: `Options` falls back to its constraint, whose
554
+ * `NoUnknownOptions` has no properties, and TypeScript only checks a value for a property
555
+ * in common with an intersection when every member has properties, all optional.
517
556
  *
518
557
  * The type parameter defaults only apply when type arguments are passed explicitly, and
519
558
  * give an `unknown` result type. An explicit `any` as the second type argument was the
@@ -525,7 +564,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
525
564
  } = TransformOptions, Paths extends readonly string[] = readonly string[]>(data: T, options: Options & {
526
565
  targetCase?: To;
527
566
  depth?: Depth;
528
- } & NoUnknownOptions<Options>): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
567
+ } & NoUnknownOptions<Options> & object): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
529
568
  /**
530
569
  * The 2.x form with explicit `<Data, SourceCase, TargetCase>` type arguments.
531
570
  *
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}function i(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}function a(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function f(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:u(t.pathsBelow,e.root)}const l="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return l}}function d(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}}function h(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}}function y(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const g={};let w=!1;function v(e,t){const n=e.get(t);if(void 0!==n||!w)return n;let r=e.get(g);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(g)}}function b(e,t,n,r){let o=e[t];o.size>=16e6&&(w=!0,o=e[t]=new Map([[g,o]])),o.set(n,r)}function C(e){return e>=48&&e<=57}function U(e){return e>=97&&e<=122}function m(e){return e>=65&&e<=90}function k(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(C(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const j="s".charCodeAt(0);function x(e,t){if(!m(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(U(n)||C(n))return!0;const r=e.charCodeAt(t+1);return!(!m(n)||!U(r))&&(r!==j||U(e.charCodeAt(t+2)))}function M(e){let t=1;for(;t<e.length&&!x(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function O(e,t,n){return e.split(t).join(n)}function A(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function _(e,t){const n=m(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}function R(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)x(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function S(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=m(r);if(!o&&!U(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),U(r))c=!0;else if(m(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!C(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return _(e,t);if(!o)switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return R(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"camel":return M(e);case"constant":return R(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return A(e,"_",!1);case"pascal":return A(e,"_",!0);case"kebab":return O(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(k(e))return e;switch(t){case"camel":return A(e,"-",!1);case"pascal":return A(e,"-",!0);case"snake":return O(e,"-","_");case"constant":return O(e,"-","_").toUpperCase();default:return e}}const $=new Map;let E,T={current:new Map,previous:new Map},N=0;function K(e,t){if(e.length>100)return S(e,t);if(t!==E){let e=$.get(t);e||(e={current:new Map,previous:new Map},$.set(t,e)),E=t,T=e}const n=T;let r=n.current.get(e);if(void 0!==r)return N=0,r;if(r=n.previous.get(e),void 0===r){if(r=S(e,t),++N>256&&N%8!=0)return r}else N=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const L={reason:"nesting limit reached"};function P(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,u=t.keepReferences;if(u){const t=v(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw L;if(Array.isArray(e)){const s=[];return u&&b(n,c,e,s),B(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return u&&b(n,c,e,r),null===t.exclude?z(e,r,t,n,i,o):D(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return u&&b(n,c,e,r),J(e,r,t,n,i,o),r}if(a===l){const r=e,s={};if(u&&b(n,c,e,s),null===t.exclude){for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){const c=K(e,t.targetCase),u=r[e];if("object"!=typeof u||null===u){y(s,c,u);continue}y(s,c,P(u,t,n,i,o+1))}}else q(r,s,t.exclude,i,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}function B(e,t,n,r,o,s){for(const c of e)"object"==typeof c&&null!==c?t.push(P(c,n,r,o,s+1)):t.push(c)}function z(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?S(c,n.targetCase):c,P(u,n,r,o,s+1))}function J(e,t,n,r,o,s){for(const c of e)t.add(P(c,n,r,o,s+1))}function q(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=a(n,s),u=f(n,c);y(t,c.keepKey?s:K(s,n.options.targetCase),P(e[s],u.options,u.visitedRefs,r,o+1))}}function D(e,t,n,r,o){for(const[s,c]of e){const e=a(n,s),u=f(n,e);t.set("string"!=typeof s||e.keepKey?s:S(s,n.options.targetCase),P(c,u.options,u.visitedRefs,r,o+1))}}const F=["camel","snake","pascal","kebab","constant"];function G(e){const n=e||"camel";if(r=n,!F.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${F.join(", ")}.`);var r;return n}function H(e,t,n){const r=void 0===n?null:i(n,t);return null===r?P(e,t):P(e,r.options,r.visitedRefs)}exports.transform=function(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=G(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return H(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==L)throw e}return H(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)},exports.transformKey=function(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return S(e,G(null==n?void 0:n.targetCase))};
1
+ "use strict";const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}function i(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}function a(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function f(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:u(t.pathsBelow,e.root)}const l="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return l}}function d(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}}function h(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}}function y(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const g={};let w=!1;function b(e,t){const n=e.get(t);if(void 0!==n||!w)return n;let r=e.get(g);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(g)}}function v(e,t,n,r){let o=e[t];o.size>=16e6&&(w=!0,o=e[t]=new Map([[g,o]])),o.set(n,r)}function C(e){return e>=48&&e<=57}function U(e){return e>=97&&e<=122}function m(e){return e>=65&&e<=90}function k(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(C(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const j="s".charCodeAt(0);function x(e,t){if(!m(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(U(n)||C(n))return!0;const r=e.charCodeAt(t+1);return!(!m(n)||!U(r))&&(r!==j||U(e.charCodeAt(t+2)))}function O(e){let t=1;for(;t<e.length&&!x(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function M(e,t,n){return e.split(t).join(n)}function A(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function _(e,t){const n=m(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}function R(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)x(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function $(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=m(r);if(!o&&!U(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),U(r))c=!0;else if(m(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!C(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return _(e,t);if(!o)switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return R(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"camel":return O(e);case"constant":return R(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return A(e,"_",!1);case"pascal":return A(e,"_",!0);case"kebab":return M(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(k(e))return e;switch(t){case"camel":return A(e,"-",!1);case"pascal":return A(e,"-",!0);case"snake":return M(e,"-","_");case"constant":return M(e,"-","_").toUpperCase();default:return e}}const S=new Map;let E,T={current:new Map,previous:new Map},N=0;function K(e,t){if(e.length>100)return $(e,t);if(t!==E){let e=S.get(t);e||(e={current:new Map,previous:new Map},S.set(t,e)),E=t,T=e}const n=T;let r=n.current.get(e);if(void 0!==r)return N=0,r;if(r=n.previous.get(e),void 0===r){if(r=$(e,t),++N>256&&N%8!=0)return r}else N=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const L={reason:"nesting limit reached"};function P(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,u=t.keepReferences;if(u){const t=b(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw L;if(Array.isArray(e)){const s=[];return u&&v(n,c,e,s),B(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return u&&v(n,c,e,r),null===t.exclude?z(e,r,t,n,i,o):q(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return u&&v(n,c,e,r),J(e,r,t,n,i,o),r}if(a===l){const r=e,s={};if(u&&v(n,c,e,s),null===t.exclude){for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){const c=K(e,t.targetCase),u=r[e];if("object"!=typeof u||null===u){y(s,c,u);continue}y(s,c,P(u,t,n,i,o+1))}}else I(r,s,t.exclude,i,o);for(const e of Object.getOwnPropertySymbols(r))Object.prototype.propertyIsEnumerable.call(r,e)&&(s[e]=r[e]);return s}return e}function B(e,t,n,r,o,s){for(const c of e)"object"==typeof c&&null!==c?t.push(P(c,n,r,o,s+1)):t.push(c)}function z(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?$(c,n.targetCase):c,P(u,n,r,o,s+1))}function J(e,t,n,r,o,s){for(const c of e)t.add(P(c,n,r,o,s+1))}function I(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=a(n,s),u=f(n,c);y(t,c.keepKey?s:K(s,n.options.targetCase),P(e[s],u.options,u.visitedRefs,r,o+1))}}function q(e,t,n,r,o){for(const[s,c]of e){const e=a(n,s),u=f(n,e);t.set("string"!=typeof s||e.keepKey?s:$(s,n.options.targetCase),P(c,u.options,u.visitedRefs,r,o+1))}}const D=["camel","snake","pascal","kebab","constant"];function F(e){return D.includes(e)}function G(e){if("string"!=typeof e)return;const n=t(e);throw new TypeError(F(e)?`Unsupported options ${n}. Use an options object: { targetCase: ${n} }.`:`Unsupported options ${n}. Use an options object, such as { targetCase: "snake" }. To pass the function as a callback, wrap it so that it gets only its first argument.`)}function H(e){const n=e||"camel";if(!F(n))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${D.join(", ")}.`);return n}function Q(e,t,n){const r=void 0===n?null:i(n,t);return null===r?P(e,t):P(e,r.options,r.visitedRefs)}exports.transform=function(e,n){var r,o;G(n);const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=H(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return Q(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==L)throw e}return Q(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)},exports.transformKey=function(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);return G(n),$(e,H(null==n?void 0:n.targetCase))};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}function i(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}function a(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function f(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:u(t.pathsBelow,e.root)}const l="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return l}}function d(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}}function h(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}}function y(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const g={};let w=!1;function v(e,t){const n=e.get(t);if(void 0!==n||!w)return n;let r=e.get(g);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(g)}}function b(e,t,n,r){let o=e[t];o.size>=16e6&&(w=!0,o=e[t]=new Map([[g,o]])),o.set(n,r)}function C(e){return e>=48&&e<=57}function U(e){return e>=97&&e<=122}function k(e){return e>=65&&e<=90}function j(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(C(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const m="s".charCodeAt(0);function x(e,t){if(!k(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(U(n)||C(n))return!0;const r=e.charCodeAt(t+1);return!(!k(n)||!U(r))&&(r!==m||U(e.charCodeAt(t+2)))}function M(e){let t=1;for(;t<e.length&&!x(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function O(e,t,n){return e.split(t).join(n)}function A(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function _(e,t){const n=k(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}function R(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)x(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function S(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=k(r);if(!o&&!U(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),U(r))c=!0;else if(k(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!C(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return _(e,t);if(!o)switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return R(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"camel":return M(e);case"constant":return R(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return A(e,"_",!1);case"pascal":return A(e,"_",!0);case"kebab":return O(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(j(e))return e;switch(t){case"camel":return A(e,"-",!1);case"pascal":return A(e,"-",!0);case"snake":return O(e,"-","_");case"constant":return O(e,"-","_").toUpperCase();default:return e}}const $=new Map;let E,T={current:new Map,previous:new Map},N=0;function L(e,t){if(e.length>100)return S(e,t);if(t!==E){let e=$.get(t);e||(e={current:new Map,previous:new Map},$.set(t,e)),E=t,T=e}const n=T;let r=n.current.get(e);if(void 0!==r)return N=0,r;if(r=n.previous.get(e),void 0===r){if(r=S(e,t),++N>256&&N%8!=0)return r}else N=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const P={reason:"nesting limit reached"};function B(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,u=t.keepReferences;if(u){const t=v(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw P;if(Array.isArray(e)){const s=[];return u&&b(n,c,e,s),K(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return u&&b(n,c,e,r),null===t.exclude?z(e,r,t,n,i,o):D(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return u&&b(n,c,e,r),J(e,r,t,n,i,o),r}if(a===l){const r=e,s={};if(u&&b(n,c,e,s),null===t.exclude){for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){const c=L(e,t.targetCase),u=r[e];if("object"!=typeof u||null===u){y(s,c,u);continue}y(s,c,B(u,t,n,i,o+1))}}else q(r,s,t.exclude,i,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}function K(e,t,n,r,o,s){for(const c of e)"object"==typeof c&&null!==c?t.push(B(c,n,r,o,s+1)):t.push(c)}function z(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?S(c,n.targetCase):c,B(u,n,r,o,s+1))}function J(e,t,n,r,o,s){for(const c of e)t.add(B(c,n,r,o,s+1))}function q(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=a(n,s),u=f(n,c);y(t,c.keepKey?s:L(s,n.options.targetCase),B(e[s],u.options,u.visitedRefs,r,o+1))}}function D(e,t,n,r,o){for(const[s,c]of e){const e=a(n,s),u=f(n,e);t.set("string"!=typeof s||e.keepKey?s:S(s,n.options.targetCase),B(c,u.options,u.visitedRefs,r,o+1))}}const F=["camel","snake","pascal","kebab","constant"];function G(e){const n=e||"camel";if(r=n,!F.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${F.join(", ")}.`);var r;return n}function H(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=G(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return I(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==P)throw e}return I(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)}function I(e,t,n){const r=void 0===n?null:i(n,t);return null===r?B(e,t):B(e,r.options,r.visitedRefs)}function Q(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return S(e,G(null==n?void 0:n.targetCase))}export{H as transform,Q as transformKey};
1
+ const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}function i(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}function a(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function f(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:u(t.pathsBelow,e.root)}const l="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return l}}function d(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}}function h(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}}function y(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const g={};let w=!1;function b(e,t){const n=e.get(t);if(void 0!==n||!w)return n;let r=e.get(g);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(g)}}function v(e,t,n,r){let o=e[t];o.size>=16e6&&(w=!0,o=e[t]=new Map([[g,o]])),o.set(n,r)}function C(e){return e>=48&&e<=57}function U(e){return e>=97&&e<=122}function k(e){return e>=65&&e<=90}function j(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(C(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const m="s".charCodeAt(0);function O(e,t){if(!k(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(U(n)||C(n))return!0;const r=e.charCodeAt(t+1);return!(!k(n)||!U(r))&&(r!==m||U(e.charCodeAt(t+2)))}function x(e){let t=1;for(;t<e.length&&!O(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function M(e,t,n){return e.split(t).join(n)}function A(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function _(e,t){const n=k(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}function R(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)O(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function $(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=k(r);if(!o&&!U(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),U(r))c=!0;else if(k(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!C(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return _(e,t);if(!o)switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return R(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return R(e,"_",!1);case"kebab":return R(e,"-",!1);case"camel":return x(e);case"constant":return R(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return A(e,"_",!1);case"pascal":return A(e,"_",!0);case"kebab":return M(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(j(e))return e;switch(t){case"camel":return A(e,"-",!1);case"pascal":return A(e,"-",!0);case"snake":return M(e,"-","_");case"constant":return M(e,"-","_").toUpperCase();default:return e}}const S=new Map;let E,T={current:new Map,previous:new Map},N=0;function L(e,t){if(e.length>100)return $(e,t);if(t!==E){let e=S.get(t);e||(e={current:new Map,previous:new Map},S.set(t,e)),E=t,T=e}const n=T;let r=n.current.get(e);if(void 0!==r)return N=0,r;if(r=n.previous.get(e),void 0===r){if(r=$(e,t),++N>256&&N%8!=0)return r}else N=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const P={reason:"nesting limit reached"};function B(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,u=t.keepReferences;if(u){const t=b(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw P;if(Array.isArray(e)){const s=[];return u&&v(n,c,e,s),K(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return u&&v(n,c,e,r),null===t.exclude?z(e,r,t,n,i,o):q(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return u&&v(n,c,e,r),J(e,r,t,n,i,o),r}if(a===l){const r=e,s={};if(u&&v(n,c,e,s),null===t.exclude){for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){const c=L(e,t.targetCase),u=r[e];if("object"!=typeof u||null===u){y(s,c,u);continue}y(s,c,B(u,t,n,i,o+1))}}else I(r,s,t.exclude,i,o);for(const e of Object.getOwnPropertySymbols(r))Object.prototype.propertyIsEnumerable.call(r,e)&&(s[e]=r[e]);return s}return e}function K(e,t,n,r,o,s){for(const c of e)"object"==typeof c&&null!==c?t.push(B(c,n,r,o,s+1)):t.push(c)}function z(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?$(c,n.targetCase):c,B(u,n,r,o,s+1))}function J(e,t,n,r,o,s){for(const c of e)t.add(B(c,n,r,o,s+1))}function I(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=a(n,s),u=f(n,c);y(t,c.keepKey?s:L(s,n.options.targetCase),B(e[s],u.options,u.visitedRefs,r,o+1))}}function q(e,t,n,r,o){for(const[s,c]of e){const e=a(n,s),u=f(n,e);t.set("string"!=typeof s||e.keepKey?s:$(s,n.options.targetCase),B(c,u.options,u.visitedRefs,r,o+1))}}const D=["camel","snake","pascal","kebab","constant"];function F(e){return D.includes(e)}function G(e){if("string"!=typeof e)return;const n=t(e);throw new TypeError(F(e)?`Unsupported options ${n}. Use an options object: { targetCase: ${n} }.`:`Unsupported options ${n}. Use an options object, such as { targetCase: "snake" }. To pass the function as a callback, wrap it so that it gets only its first argument.`)}function H(e){const n=e||"camel";if(!F(n))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${D.join(", ")}.`);return n}function Q(e,n){var r,o;G(n);const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=H(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return V(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==P)throw e}return V(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)}function V(e,t,n){const r=void 0===n?null:i(n,t);return null===r?B(e,t):B(e,r.options,r.visitedRefs)}function W(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);return G(n),$(e,H(null==n?void 0:n.targetCase))}export{Q as transform,W as transformKey};
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "deep-case-crafter",
3
- "version": "3.4.1",
4
- "description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, or to CONSTANT_CASE, deeply (objects, arrays, Map, Set), with exact TypeScript types",
3
+ "version": "3.5.0",
4
+ "description": "Convert object keys between camelCase and snake_case, deeply, with exact TypeScript types",
5
+ "type": "commonjs",
5
6
  "main": "dist/index.js",
6
7
  "module": "dist/index.mjs",
7
8
  "types": "dist/index.d.ts",
@@ -15,14 +16,16 @@
15
16
  "types": "./dist/index.d.cts",
16
17
  "default": "./dist/index.js"
17
18
  }
18
- }
19
+ },
20
+ "./package.json": "./package.json"
19
21
  },
20
22
  "files": [
21
23
  "dist"
22
24
  ],
25
+ "sideEffects": false,
23
26
  "repository": {
24
27
  "type": "git",
25
- "url": "https://github.com/gyrgy/deep-case-crafter.git"
28
+ "url": "git+https://github.com/gyrgy/deep-case-crafter.git"
26
29
  },
27
30
  "bugs": {
28
31
  "url": "https://github.com/gyrgy/deep-case-crafter/issues"
package/readme.md CHANGED
@@ -26,6 +26,18 @@ const body = transform(user, { targetCase: 'snake' });
26
26
  // ^? { user_id: number; first_name: string; orders: { order_id: number }[] }
27
27
  ```
28
28
 
29
+ | For | Use |
30
+ | ---------------------------------------- | ------------------------------------------------------------------ |
31
+ | camelCase keys | `transform(data)` |
32
+ | snake_case keys | `transform(data, { targetCase: 'snake' })` |
33
+ | kebab-case, PascalCase or CONSTANT_CASE | `targetCase: 'kebab'`, `'pascal'` or `'constant'` |
34
+ | One key | `transformKey('user_id')` → `'userId'` |
35
+ | Some keys left as they are | `transform(data, { exclude: ['users_by_id.*'] })` |
36
+ | The top level only | `transform(data, { depth: 1 })` |
37
+ | Faster conversion of `JSON.parse` output | `transform(data, { keepReferences: false })` |
38
+ | `transform` as a callback | `rows.map((row) => transform(row))` |
39
+ | The result type, or a key's type | `TransformResult<typeof data, 'snake'>`, `TransformKey<'user_id'>` |
40
+
29
41
  ## Features
30
42
 
31
43
  - **Exact result types**: every key is typed the way it is converted at runtime, so
@@ -193,8 +205,8 @@ function toSortParam(field: SortField) {
193
205
 
194
206
  ## How it compares
195
207
 
196
- Checked in October 2026 against
197
- [ts-case-convert](https://www.npmjs.com/package/ts-case-convert) 2.3.1 (the closest
208
+ Checked in October and November 2026 against
209
+ [ts-case-convert](https://www.npmjs.com/package/ts-case-convert) 2.3.2 (the closest
198
210
  typed alternative), [camelcase-keys](https://www.npmjs.com/package/camelcase-keys)
199
211
  10.0.3, [snakecase-keys](https://www.npmjs.com/package/snakecase-keys) 9.0.2 and
200
212
  [es-toolkit](https://www.npmjs.com/package/es-toolkit) 1.53.0 (`toCamelCaseKeys`,
@@ -218,7 +230,7 @@ Where the others are better or different:
218
230
 
219
231
  - **Bundle size**: bundled and minified with esbuild, ts-case-convert's `objectToCamel`
220
232
  and `objectToSnake` take about 1.1 KB gzipped, deep-case-crafter's `transform` about
221
- 2.8 KB.
233
+ 2.9 KB.
222
234
  - **CONSTANT_CASE input**: camelcase-keys, snakecase-keys and es-toolkit also read
223
235
  SCREAMING_SNAKE_CASE keys, so `USER_ID` becomes `userId` or `user_id`.
224
236
  deep-case-crafter converts to CONSTANT_CASE, but leaves keys such as `USER_ID` and
@@ -304,11 +316,12 @@ The result **types** match all of this, so `result.get_http_response` and
304
316
  ### `transform(data, options)`
305
317
 
306
318
  - **data**: The data structure to transform (`object`, `array`, `Map`, or `Set`).
307
- - **options**:
319
+ - **options**: An object. Passing the target case itself, `transform(data, 'snake')`,
320
+ does not compile and throws a `TypeError`.
308
321
  - `targetCase` (optional): `'camel' | 'snake' | 'pascal' | 'kebab' | 'constant'` (default: `'camel'`, also used for any falsy value such as `''`, `null` or `0`). Any other value throws a `TypeError`. `'constant'` is CONSTANT_CASE, also called SCREAMING_SNAKE_CASE: `userID` becomes `USER_ID`, split into words as for snake_case. It is a target case only: keys such as `USER_ID` or `ID` are left unchanged by every target case, so they do not convert back. A single capital, with or without digits, is still converted as a one-letter word: `'constant'` turns `p99` into `P99`, and camel, snake and kebab turn it back into `p99` (see [Key Preservation Rules](#key-preservation-rules)).
309
322
  - `depth` (optional): **How many nesting levels to transform** (default: `Infinity` — the whole structure; `null` means the same). `1` transforms only the top-level keys, `2` the keys one level down as well, and so on. The values of objects, Maps and Sets are one level deeper; array items stay on their array's level. A value that is not a number (`NaN`, `'3'`, `3n`) throws a `TypeError`.
310
323
  - `exclude` (optional): **Keys to leave unchanged**, as paths of keys joined with dots, such as `['users_by_id.*', 'metadata.*']` (default: none; `null` means the same). See [Leaving keys unchanged](#leaving-keys-unchanged). Anything but an array of such paths throws a `TypeError`.
311
- - `keepReferences` (optional): **Whether shared and circular references keep their shape** (default: `true`; `null` means the same). An object reached through several references then gives one result, and a circular reference points back into the result. Pass `false` for data that cannot contain such references, such as the output of `JSON.parse`, to skip that bookkeeping: inputs with many objects and arrays convert up to about twice as fast. Use it for tree-shaped data only: an object is then copied once for every path that leads to it, so shared objects inside shared objects multiply. Data with more than 256 nested objects, arrays, Maps and Sets on one path, as circular references always have, is converted again from the start with the bookkeeping, so it takes longer and getters can run twice. With a finite `depth`, circular references stop at the depth limit before that, and are copied once per path down to it. Anything but a boolean throws a `TypeError`.
324
+ - `keepReferences` (optional): **Whether shared and circular references keep their shape** (default: `true`; `null` means the same). An object reached through several references then gives one result, and a circular reference points back into the result. Pass `false` for data that cannot contain such references, such as the output of `JSON.parse`, to skip that bookkeeping: inputs with many objects and arrays convert up to about twice as fast. Use it for tree-shaped data only: an object is then copied once for every path that leads to it, so shared objects inside shared objects multiply. Data with more than 256 nested objects, arrays, Maps and Sets on one path, as circular references always have, is converted again from the start with the bookkeeping. That takes longer and runs its getters again; a getter on a circular path has by then run up to 256 times. With a finite `depth`, a circular reference stops at the depth limit instead when that comes first, and is copied once per path down to it. Anything but a boolean throws a `TypeError`.
312
325
  - `sourceCase` (**deprecated**): has no effect. Every key's case is detected from the key itself, at runtime and in the result type. It is still accepted, and so is the 2.x form with explicit type arguments, `transform<Data, 'snake', 'camel'>(data, { sourceCase: 'snake', targetCase: 'camel' })`, except together with `depth`, `exclude` or `keepReferences` (leave the type arguments out to use them); the key types follow the runtime, not `sourceCase` (see [3.0.0 in the changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md#300---2026-10-25)).
313
326
 
314
327
  Circular references are resolved to the transformed object. With a finite `depth`, a
@@ -357,7 +370,9 @@ known as `string[]` give an `unknown` result type.
357
370
  Wrap it in a function: `.then((data) => transform(data))`,
358
371
  `rows.map((row) => transform(row))`. Passed by name, `transform` gets an `unknown` result
359
372
  type, because TypeScript then uses its last overload, `transform<Input, Output>`, whose
360
- output it cannot infer, and `map` would pass each row's index as the options.
373
+ output it cannot infer, and `map` would pass each row's index as the options. A callback
374
+ that passes a string, such as the key in lodash's `mapValues(object, transform)`, makes it
375
+ throw a `TypeError`.
361
376
 
362
377
  ### Result types
363
378
 
@@ -386,7 +401,9 @@ type of the `exclude` paths, such as `['metadata.*']`.
386
401
  symbol-keyed members fail to compile (TS7056, TS4118). For a target case typed
387
402
  `StringCase`, generic data included, the result can cover only the cases that existed
388
403
  when the library was built, not cases added later. A generic target case, as in
389
- `<C extends StringCase>(targetCase: C)`, is not affected.
404
+ `<C extends StringCase>(targetCase: C)`, is not affected. Leave its result type
405
+ inferred: `TransformResult<T, C>` and `TransformKey<K, C>` do not match it, and its
406
+ declarations refer to the types by name.
390
407
  - Some values cannot be told apart by their types. `Error`, `URL`, boxed primitives,
391
408
  iterators, module namespace objects and other built-ins whose types do not declare a
392
409
  `Symbol.toStringTag` are returned untouched, but typed as plain objects with
@@ -404,7 +421,7 @@ Converts a single key by the same rules as the keys of `transform`'s result.
404
421
  - **key**: The key to transform, a string. Anything else throws a `TypeError`.
405
422
  - **options**:
406
423
  - `targetCase` (optional): as for `transform` (default: `'camel'`). Pass it in an
407
- object: `transformKey(key, 'snake')` throws a `TypeError`.
424
+ object: `transformKey(key, 'snake')` does not compile and throws a `TypeError`.
408
425
 
409
426
  ```typescript
410
427
  transformKey('HTTPResponse', { targetCase: 'snake' }); // 'http_response'
@@ -487,16 +504,16 @@ paths in [`exclude`](#leaving-keys-unchanged).
487
504
  | Primitives, functions and built-ins such as `Date`, `RegExp`, `Error`, `URL`, `Promise`, typed arrays, `Buffer` | Returned untouched (the same reference) |
488
505
  | Objects with a `Symbol.toStringTag` of their own or from their class, `arguments` objects, module namespaces, and Proxies around a Map or Set | Returned untouched (the same reference) |
489
506
 
490
- Symbol-keyed properties are copied unchanged.
507
+ Enumerable symbol-keyed properties are copied, with their values untransformed.
491
508
 
492
509
  Worth knowing:
493
510
 
494
511
  - If two keys of one object become the same key (`user_id` and `userId` both become
495
512
  `userId`), the last one wins.
496
- - Every property is read once, in order, and its value is transformed before the next one
497
- is read; getters run during the transformation. Maps are read through their iterator.
498
- With `keepReferences: false`, data with more than 256 nested objects, arrays, Maps and
499
- Sets on one path is read a second time.
513
+ - Every property is read in order, and its value is transformed before the next one is
514
+ read; getters run during the transformation. Maps are read through their iterator. An
515
+ object is read once for every copy made of it, which is one copy unless
516
+ `keepReferences: false` (see the option), `depth` or `exclude` (below) make several.
500
517
  - With `depth`, values below the limit are returned as they are: the result shares them
501
518
  with the input instead of copying them. An object reached at several levels is
502
519
  transformed once per level.
@@ -514,10 +531,10 @@ Measured with Node 24 on an Apple M4 Pro, with Node's default stack and memory s
514
531
 
515
532
  - **Nesting depth.** The conversion is recursive. Depending on the shape, the first call
516
533
  in a process converts 1,300 to 2,300 levels of nesting (nested Maps the fewest, nested
517
- objects the most), and once the code is warm about 2,600 (nested arrays) to 5,200;
518
- deeper data throws a `RangeError`. API data nests far less, but untrusted input can nest
519
- deeper (`JSON.parse` accepts millions of levels), so catch the error where such input is
520
- converted.
534
+ objects the most); deeper data throws a `RangeError`. Later calls can go deeper, up to
535
+ about 5,200 levels, depending on the data converted before. API data nests far less,
536
+ but untrusted input can nest deeper (`JSON.parse` accepts millions of levels), so catch
537
+ the error where such input is converted.
521
538
  - **Size.** There is no fixed limit on keys, objects or array items. The result is a full
522
539
  copy, so the input and the result must both fit in memory: converting 1 million records
523
540
  with 30 million keys (725 MB in memory) needed 1.5 GB more at its peak, and 1.1 GB with
@@ -536,7 +553,9 @@ stable. Key outputs change only to fix bugs, and every change is listed in the
536
553
  store converted keys, such as database columns, check it before upgrading. A minor
537
554
  version can add an option, which `Required<TransformOptions>` and
538
555
  `Required<TransformKeyOptions>` then require, or a target case, which code that handles
539
- every member of `StringCase` then has to handle too.
556
+ every member of `StringCase` then has to handle too. It can also reject at compile time
557
+ options that the runtime ignores or rejects, as 3.5.0 did for a string or a number in
558
+ place of the options object.
540
559
 
541
560
  ### Upgrading to v3
542
561