deep-case-crafter 3.4.1 → 3.6.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 +60 -21
- package/dist/index.d.mts +60 -21
- package/dist/index.d.ts +60 -21
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +11 -7
- package/readme.md +43 -24
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
|
|
88
|
-
* getters
|
|
89
|
-
*
|
|
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
|
|
112
|
-
*
|
|
113
|
-
* it does for every type whose properties are all optional: `'snake'`,
|
|
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[]> =
|
|
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,
|
|
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<
|
|
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
|
-
/**
|
|
353
|
-
|
|
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 :
|
|
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 :
|
|
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
|
|
88
|
-
* getters
|
|
89
|
-
*
|
|
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
|
|
112
|
-
*
|
|
113
|
-
* it does for every type whose properties are all optional: `'snake'`,
|
|
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[]> =
|
|
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,
|
|
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<
|
|
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
|
-
/**
|
|
353
|
-
|
|
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 :
|
|
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 :
|
|
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
|
|
88
|
-
* getters
|
|
89
|
-
*
|
|
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
|
|
112
|
-
*
|
|
113
|
-
* it does for every type whose properties are all optional: `'snake'`,
|
|
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[]> =
|
|
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,
|
|
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<
|
|
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
|
-
/**
|
|
353
|
-
|
|
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 :
|
|
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 :
|
|
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
|
|
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 u(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 u=new Map;for(const[e,t]of null!=s?s:[])u.set(e,o(r.length?[...t,...r]:t));const{targetCase:c,depth:i,keepReferences:a}=t.plain.options,l={targetCase:c,depth:i,keepReferences:a,exclude:null,layouts:null},f={options:l,visitedRefs:[],named:u,other:o(r),root:t};return l.exclude=f,f}function c(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=u(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 u(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 l(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:c(t.pathsBelow,e.root)}const f="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return f}}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}function g(e,t){let n=0;for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&(t[r]=e[r],n++);return n}function w(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let e=0;e<n.length;e++)if(n[e]!==r[e])return!1;return!0}function b(e,t,n,r){var o;if(n>200)return;const s=null!==(o=e.layouts)&&void 0!==o?o:e.layouts={done:!1,seenWidths:[],seenKeys:[],seenResults:[],nextSeen:0,fastWidths:[],fastKeys:[],fastObjects:[]};if(s.done)return;const{fastWidths:u,fastKeys:c}=s;for(let e=0;e<u.length;e++)if(u[e]===n&&c[e]===r)return;const{seenWidths:i,seenKeys:a,seenResults:l}=s;for(let e=0;e<i.length;e++)if(i[e]===n&&a[e]===r)return void(w(l[e],t)?(u.push(n),c.push(r),s.fastObjects.push({...t}),s.done=16===u.length):s.done=!0);i[s.nextSeen]=n,a[s.nextSeen]=r,l[s.nextSeen]=t,s.nextSeen=(s.nextSeen+1)%16}const v={};let C=!1;function j(e,t){const n=e.get(t);if(void 0!==n||!C)return n;let r=e.get(v);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(v)}}function k(e,t,n,r){let o=e[t];o.size>=16e6&&(C=!0,o=e[t]=new Map([[v,o]])),o.set(n,r)}function U(e){return e>=48&&e<=57}function m(e){return e>=97&&e<=122}function x(e){return e>=65&&e<=90}function O(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(!(U(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const S="s".charCodeAt(0);function M(e,t){if(!x(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(m(n)||U(n))return!0;const r=e.charCodeAt(t+1);return!(!x(n)||!m(r))&&(r!==S||m(e.charCodeAt(t+2)))}function R(e){let t=1;for(;t<e.length&&!M(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function A(e,t,n){return e.split(t).join(n)}function _(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=x(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 E(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)M(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function K(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=x(r);if(!o&&!m(r))return e;let s=o?1:0,u=!o,c=0,i=0,a=!1,l=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),m(r))u=!0;else if(x(r))s++;else if(95===r)c++,95===l&&(a=!0);else if(45===r)i++,45===l&&(a=!0);else if(!U(r))return e;l=r}if(95===l||45===l)return e;if(0===c&&0===i){if(0===s||1===s&&o)return $(e,t);if(!o)switch(t){case"snake":return E(e,"_",!1);case"kebab":return E(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return E(e,"_",!0);default:return e}if(!u)return e;switch(t){case"snake":return E(e,"_",!1);case"kebab":return E(e,"-",!1);case"camel":return R(e);case"constant":return E(e,"_",!0);default:return e}}if(s>0||a||c>0&&i>0)return e;if(c>0)switch(t){case"camel":return _(e,"_",!1);case"pascal":return _(e,"_",!0);case"kebab":return A(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(O(e))return e;switch(t){case"camel":return _(e,"-",!1);case"pascal":return _(e,"-",!0);case"snake":return A(e,"-","_");case"constant":return A(e,"-","_").toUpperCase();default:return e}}const T=new Map;let N,L={current:new Map,previous:new Map},P=0;function W(e,t){if(e.length>100)return K(e,t);if(t!==N){let e=T.get(t);e||(e={current:new Map,previous:new Map},T.set(t,e)),N=t,L=e}const n=L;let r=n.current.get(e);if(void 0!==r)return P=0,r;if(r=n.previous.get(e),void 0===r){if(r=K(e,t),++P>256&&P%8!=0)return r}else P=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const B={reason:"nesting limit reached"};function z(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const u=t.depth===1/0?0:r,c=t.keepReferences;if(c){const t=j(null!==(s=n[u])&&void 0!==s?s:n[u]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw B;if(Array.isArray(e)){const s=[];return c&&k(n,u,e,s),J(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return c&&k(n,u,e,r),null===t.exclude?I(e,r,t,n,i,o):F(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return c&&k(n,u,e,r),q(e,r,t,n,i,o),r}if(a===f){const r=e,s={};if(c&&k(n,u,e,s),null!==t.exclude)return D(r,s,t.exclude,i,o),s;let a=0,l="";for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){l=W(e,t.targetCase),a++;const u=r[e];if("object"!=typeof u||null===u){y(s,l,u);continue}y(s,l,z(u,t,n,i,o+1))}return a+=g(r,s),a>=20&&(null===t.layouts||!t.layouts.done)&&b(t,s,a,l),s}return e}function J(e,t,n,r,o,s){for(const u of e)"object"==typeof u&&null!==u?t.push(z(u,n,r,o,s+1)):t.push(u)}function I(e,t,n,r,o,s){for(const[u,c]of e)t.set("string"==typeof u?K(u,n.targetCase):u,z(c,n,r,o,s+1))}function q(e,t,n,r,o,s){for(const u of e)t.add(z(u,n,r,o,s+1))}function D(e,t,n,r,o){let s=0,u="";for(const c in e)if(Object.prototype.hasOwnProperty.call(e,c)){const i=a(n,c),f=l(n,i);u=i.keepKey?c:W(c,n.options.targetCase),s++,y(t,u,z(e[c],f.options,f.visitedRefs,r,o+1))}s+=g(e,t),s>=20&&b(n.options,t,s,u)}function F(e,t,n,r,o){for(const[s,u]of e){const e=a(n,s),c=l(n,e);t.set("string"!=typeof s||e.keepKey?s:K(s,n.options.targetCase),z(u,c.options,c.visitedRefs,r,o+1))}}const G=["camel","snake","pascal","kebab","constant"];function H(e){return G.includes(e)}function Q(e){if("string"!=typeof e)return;const n=t(e);throw new TypeError(H(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 V(e){const n=e||"camel";if(!H(n))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${G.join(", ")}.`);return n}function X(e,t,n){const r=void 0===n?null:i(n,t);return null===r?z(e,t):z(e,r.options,r.visitedRefs)}exports.transform=function(e,n){var r,o;Q(n);const s=null==n?void 0:n.targetCase,u=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,c=V(s);if("number"!=typeof u||Number.isNaN(u))throw new TypeError(`Unsupported depth ${t(u)}. 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 X(e,{targetCase:c,depth:u,keepReferences:a,exclude:null,layouts:null},i)}catch(e){if(e!==B)throw e}return X(e,{targetCase:c,depth:u,keepReferences:!0,exclude:null,layouts:null},i)},exports.transformKey=function(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);return Q(n),K(e,V(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
|
|
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 u(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 u=new Map;for(const[e,t]of null!=s?s:[])u.set(e,o(r.length?[...t,...r]:t));const{targetCase:c,depth:i,keepReferences:a}=t.plain.options,l={targetCase:c,depth:i,keepReferences:a,exclude:null,layouts:null},f={options:l,visitedRefs:[],named:u,other:o(r),root:t};return l.exclude=f,f}function c(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=u(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 u(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 l(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:c(t.pathsBelow,e.root)}const f="[object Object]";function p(e){try{return Object.prototype.toString.call(e)}catch(e){return f}}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}function g(e,t){let n=0;for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&(t[r]=e[r],n++);return n}function w(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let e=0;e<n.length;e++)if(n[e]!==r[e])return!1;return!0}function b(e,t,n,r){var o;if(n>200)return;const s=null!==(o=e.layouts)&&void 0!==o?o:e.layouts={done:!1,seenWidths:[],seenKeys:[],seenResults:[],nextSeen:0,fastWidths:[],fastKeys:[],fastObjects:[]};if(s.done)return;const{fastWidths:u,fastKeys:c}=s;for(let e=0;e<u.length;e++)if(u[e]===n&&c[e]===r)return;const{seenWidths:i,seenKeys:a,seenResults:l}=s;for(let e=0;e<i.length;e++)if(i[e]===n&&a[e]===r)return void(w(l[e],t)?(u.push(n),c.push(r),s.fastObjects.push({...t}),s.done=16===u.length):s.done=!0);i[s.nextSeen]=n,a[s.nextSeen]=r,l[s.nextSeen]=t,s.nextSeen=(s.nextSeen+1)%16}const v={};let C=!1;function j(e,t){const n=e.get(t);if(void 0!==n||!C)return n;let r=e.get(v);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(v)}}function k(e,t,n,r){let o=e[t];o.size>=16e6&&(C=!0,o=e[t]=new Map([[v,o]])),o.set(n,r)}function U(e){return e>=48&&e<=57}function m(e){return e>=97&&e<=122}function x(e){return e>=65&&e<=90}function O(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(!(U(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}const S="s".charCodeAt(0);function M(e,t){if(!x(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(m(n)||U(n))return!0;const r=e.charCodeAt(t+1);return!(!x(n)||!m(r))&&(r!==S||m(e.charCodeAt(t+2)))}function R(e){let t=1;for(;t<e.length&&!M(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}function A(e,t,n){return e.split(t).join(n)}function _(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=x(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 E(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)M(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function T(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=x(r);if(!o&&!m(r))return e;let s=o?1:0,u=!o,c=0,i=0,a=!1,l=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),m(r))u=!0;else if(x(r))s++;else if(95===r)c++,95===l&&(a=!0);else if(45===r)i++,45===l&&(a=!0);else if(!U(r))return e;l=r}if(95===l||45===l)return e;if(0===c&&0===i){if(0===s||1===s&&o)return $(e,t);if(!o)switch(t){case"snake":return E(e,"_",!1);case"kebab":return E(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return E(e,"_",!0);default:return e}if(!u)return e;switch(t){case"snake":return E(e,"_",!1);case"kebab":return E(e,"-",!1);case"camel":return R(e);case"constant":return E(e,"_",!0);default:return e}}if(s>0||a||c>0&&i>0)return e;if(c>0)switch(t){case"camel":return _(e,"_",!1);case"pascal":return _(e,"_",!0);case"kebab":return A(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(O(e))return e;switch(t){case"camel":return _(e,"-",!1);case"pascal":return _(e,"-",!0);case"snake":return A(e,"-","_");case"constant":return A(e,"-","_").toUpperCase();default:return e}}const K=new Map;let N,L={current:new Map,previous:new Map},P=0;function W(e,t){if(e.length>100)return T(e,t);if(t!==N){let e=K.get(t);e||(e={current:new Map,previous:new Map},K.set(t,e)),N=t,L=e}const n=L;let r=n.current.get(e);if(void 0!==r)return P=0,r;if(r=n.previous.get(e),void 0===r){if(r=T(e,t),++P>256&&P%8!=0)return r}else P=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const B={reason:"nesting limit reached"};function z(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const u=t.depth===1/0?0:r,c=t.keepReferences;if(c){const t=j(null!==(s=n[u])&&void 0!==s?s:n[u]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw B;if(Array.isArray(e)){const s=[];return c&&k(n,u,e,s),J(e,s,t,n,r,o),s}const i=r+1,a=p(e);if(d(e,a)){const r=new Map;return c&&k(n,u,e,r),null===t.exclude?I(e,r,t,n,i,o):F(e,r,t.exclude,i,o),r}if(h(e,a)){const r=new Set;return c&&k(n,u,e,r),q(e,r,t,n,i,o),r}if(a===f){const r=e,s={};if(c&&k(n,u,e,s),null!==t.exclude)return D(r,s,t.exclude,i,o),s;let a=0,l="";for(const e in r)if(Object.prototype.hasOwnProperty.call(r,e)){l=W(e,t.targetCase),a++;const u=r[e];if("object"!=typeof u||null===u){y(s,l,u);continue}y(s,l,z(u,t,n,i,o+1))}return a+=g(r,s),a>=20&&(null===t.layouts||!t.layouts.done)&&b(t,s,a,l),s}return e}function J(e,t,n,r,o,s){for(const u of e)"object"==typeof u&&null!==u?t.push(z(u,n,r,o,s+1)):t.push(u)}function I(e,t,n,r,o,s){for(const[u,c]of e)t.set("string"==typeof u?T(u,n.targetCase):u,z(c,n,r,o,s+1))}function q(e,t,n,r,o,s){for(const u of e)t.add(z(u,n,r,o,s+1))}function D(e,t,n,r,o){let s=0,u="";for(const c in e)if(Object.prototype.hasOwnProperty.call(e,c)){const i=a(n,c),f=l(n,i);u=i.keepKey?c:W(c,n.options.targetCase),s++,y(t,u,z(e[c],f.options,f.visitedRefs,r,o+1))}s+=g(e,t),s>=20&&b(n.options,t,s,u)}function F(e,t,n,r,o){for(const[s,u]of e){const e=a(n,s),c=l(n,e);t.set("string"!=typeof s||e.keepKey?s:T(s,n.options.targetCase),z(u,c.options,c.visitedRefs,r,o+1))}}const G=["camel","snake","pascal","kebab","constant"];function H(e){return G.includes(e)}function Q(e){if("string"!=typeof e)return;const n=t(e);throw new TypeError(H(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 V(e){const n=e||"camel";if(!H(n))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${G.join(", ")}.`);return n}function X(e,n){var r,o;Q(n);const s=null==n?void 0:n.targetCase,u=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,c=V(s);if("number"!=typeof u||Number.isNaN(u))throw new TypeError(`Unsupported depth ${t(u)}. 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 Y(e,{targetCase:c,depth:u,keepReferences:a,exclude:null,layouts:null},i)}catch(e){if(e!==B)throw e}return Y(e,{targetCase:c,depth:u,keepReferences:!0,exclude:null,layouts:null},i)}function Y(e,t,n){const r=void 0===n?null:i(n,t);return null===r?z(e,t):z(e,r.options,r.visitedRefs)}function Z(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);return Q(n),T(e,V(null==n?void 0:n.targetCase))}export{X as transform,Z as transformKey};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-case-crafter",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Convert object keys between camelCase
|
|
3
|
+
"version": "3.6.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"
|
|
@@ -63,13 +66,14 @@
|
|
|
63
66
|
"test-coverage": "npm test -- --coverage",
|
|
64
67
|
"test:types": "tsc --noEmit",
|
|
65
68
|
"tsc": "tsc --noEmit",
|
|
69
|
+
"test:dist-objects": "node --allow-natives-syntax dist-tests/fastObjects.mjs",
|
|
66
70
|
"test:dist-types": "tsc -p dist-type-tests && tsc -p dist-type-tests/tsconfig.declarations.json && node dist-type-tests/checkDeclarations.mjs && tsc -p dist-type-tests/tsconfig.consumer.json && tsc -p dist-type-tests/tsconfig.loose.json && tsc -p dist-type-tests/tsconfig.exact.json",
|
|
67
71
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src/ --max-warnings=0 --no-cache",
|
|
68
72
|
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx src/ --fix --max-warnings=0 --no-cache",
|
|
69
|
-
"format": "prettier --write src dist-type-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
|
|
70
|
-
"format:check": "prettier --check src dist-type-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
|
|
73
|
+
"format": "prettier --write src dist-type-tests dist-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
|
|
74
|
+
"format:check": "prettier --check src dist-type-tests dist-tests benchmark .github readme.md CHANGELOG.md SECURITY.md",
|
|
71
75
|
"benchmark": "tsx benchmark/*.benchmark.ts",
|
|
72
|
-
"check": "npm run lint && npm run format:check && npm run test:types && npm run test-coverage && npm run build && npm run test:dist-types"
|
|
76
|
+
"check": "npm run lint && npm run format:check && npm run test:types && npm run test-coverage && npm run build && npm run test:dist-types && npm run test:dist-objects"
|
|
73
77
|
},
|
|
74
78
|
"devDependencies": {
|
|
75
79
|
"@eslint/js": "10.0.1",
|
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.
|
|
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
|
|
233
|
+
3.2 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
|
|
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
|
-
|
|
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
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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.
|
|
@@ -513,16 +530,16 @@ Worth knowing:
|
|
|
513
530
|
Measured with Node 24 on an Apple M4 Pro, with Node's default stack and memory sizes:
|
|
514
531
|
|
|
515
532
|
- **Nesting depth.** The conversion is recursive. Depending on the shape, the first call
|
|
516
|
-
in a process converts 1,300 to 2,
|
|
517
|
-
objects the most)
|
|
518
|
-
|
|
519
|
-
deeper (`JSON.parse` accepts millions of levels), so catch
|
|
520
|
-
converted.
|
|
533
|
+
in a process converts 1,300 to 2,400 levels of nesting (nested Maps the fewest, nested
|
|
534
|
+
objects the most); deeper data throws a `RangeError`. Later calls can go deeper, up to
|
|
535
|
+
about 4,800 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
|
-
|
|
524
|
-
`keepReferences: false`. When memory runs out,
|
|
525
|
-
caught.
|
|
540
|
+
of 30 keys (260 MB in memory, as `JSON.parse` returns them) took 0.7 seconds and needed
|
|
541
|
+
420 MB more at its peak, and 370 MB with `keepReferences: false`. When memory runs out,
|
|
542
|
+
Node ends the process, which cannot be caught.
|
|
526
543
|
- **Long keys.** Conversion time grows linearly with a key's length: about 20 ms for a key
|
|
527
544
|
of a million characters.
|
|
528
545
|
|
|
@@ -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
|
|
|
@@ -551,8 +570,8 @@ in the changelog.
|
|
|
551
570
|
1. Fork the repository.
|
|
552
571
|
2. Create a new branch (`feature/my-feature`).
|
|
553
572
|
3. Commit your changes.
|
|
554
|
-
4. Run `npm run check` (lint, formatting, types, tests with coverage, build
|
|
555
|
-
published types).
|
|
573
|
+
4. Run `npm run check` (lint, formatting, types, tests with coverage, build, the
|
|
574
|
+
published types and the fast objects of the build).
|
|
556
575
|
5. Submit a pull request.
|
|
557
576
|
|
|
558
577
|
---
|