deep-case-crafter 3.0.0 → 3.1.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.mts CHANGED
@@ -48,6 +48,20 @@ interface TransformOptions {
48
48
  * @default Infinity
49
49
  */
50
50
  depth?: number | undefined;
51
+ /**
52
+ * Keys to leave unchanged, each given as its path: the keys of the input that lead to
53
+ * it, joined with `.`. `'metadata.*'` keeps the keys of `metadata`, `'users_by_id.*'`
54
+ * the IDs of a record. `*` stands for any key. Array items and Set members add no key
55
+ * to a path; Map keys do, and `*` also matches a Map key that is not a string. A key
56
+ * that contains a `.` can only be matched by `*`. Only the keys themselves are kept:
57
+ * the values under them are transformed as usual.
58
+ *
59
+ * Pass the paths inline, or `as const`, to get a precise result type. Paths only known
60
+ * as `string[]` give an `unknown` result type.
61
+ *
62
+ * @default []
63
+ */
64
+ exclude?: readonly string[] | undefined;
51
65
  /**
52
66
  * @deprecated Has no effect. Every key's case is detected from the key itself, both at
53
67
  * runtime and in the result type, so there is nothing to declare.
@@ -74,6 +88,39 @@ type TargetCaseOf<Options> = 'targetCase' extends keyof Options ? CaseOrDefault<
74
88
  * `depth` is omitted.
75
89
  */
76
90
  type DepthOf<Options> = 'depth' extends keyof Options ? Options['depth'] : undefined;
91
+ /**
92
+ * The `exclude` paths the runtime uses for one options object: `undefined` (none) when
93
+ * `exclude` is omitted.
94
+ */
95
+ type ExcludeOf<Options> = 'exclude' extends keyof Options ? Options['exclude'] : undefined;
96
+
97
+ /**
98
+ * Type-level mirror of the `exclude` matching (`excludePaths.ts`).
99
+ *
100
+ * The paths that still match at a level are a union of strings: the rest of each
101
+ * `exclude` path below the keys that lead there. `'users_by_id.*' | 'metadata.*'` at the
102
+ * top becomes `'*'` below `users_by_id`. `never` stands for no path.
103
+ */
104
+ /**
105
+ * A key type `K` as the paths name its keys: a single key as its string literal, so that
106
+ * a string enum member or a number matches the key a path names, and any other key type
107
+ * (`string`, a branded `string`, a template literal pattern) as it is.
108
+ */
109
+ type KeyName<K extends string | number> = K extends string ? IsStringLiteral<K> extends true ? `${K}` : K : `${K}`;
110
+ /** The paths of `Paths` that end at this level: the keys they match keep their names */
111
+ type PathEnds<Paths extends string> = Paths extends `${string}.${string}` ? never : Paths;
112
+ /**
113
+ * The rest of each path of `Paths` that continues below the key `Key`, or below every
114
+ * key that no path names when `Key` is `'*'`
115
+ */
116
+ type PathsBelow<Paths extends string, Key extends string> = Paths extends `${infer Head}.${infer Rest}` ? Head extends Key | '*' ? Rest : never : never;
117
+ /**
118
+ * The keys that a key type `K` which is not a single literal (an index signature) may
119
+ * stand for, as far as `Paths` is concerned: each key in it that a path continues below,
120
+ * and `'*'` for every other key. A key is in `K` when they share a value, as `'u1'` does
121
+ * with a branded `string`.
122
+ */
123
+ type KeyCases<Paths extends string, K extends string> = (Paths extends `${infer Head}.${string}` ? Head extends '*' ? never : [Head & K] extends [never] ? never : Head : never) | '*';
77
124
 
78
125
  /**
79
126
  * Character classes used by the type-level key transformation. They spell out the
@@ -86,6 +133,10 @@ type LowerLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | '
86
133
  type UpperLetter = Uppercase<LowerLetter>;
87
134
  /** ASCII digits */
88
135
  type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
136
+ /** The letters of hexadecimal digits */
137
+ type HexLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
138
+ /** Hexadecimal digits, in either case */
139
+ type HexDigit = Digit | HexLetter | Uppercase<HexLetter>;
89
140
  /** The word separators of snake_case and kebab-case */
90
141
  type Separator = '_' | '-';
91
142
  /** Every character a transformable key may contain (the runtime's `A-Z a-z 0-9 _ -`) */
@@ -109,12 +160,21 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
109
160
  * (tail-recursive, so long keys compile).
110
161
  */
111
162
  type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
163
+ /** The canonical UUID form, where `x` stands for a hexadecimal digit (`UUID_REGEX`) */
164
+ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
165
+ /**
166
+ * `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
167
+ * character at a time, stopping at the first character that does not fit, so long keys
168
+ * stay cheap.
169
+ */
170
+ type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern extends `${infer P}${infer PatternRest}` ? S extends `${infer C}${infer Rest}` ? C extends (P extends 'x' ? HexDigit : P) ? IsUuid<Rest, PatternRest> : false : false : S extends '' ? true : false;
112
171
  /**
113
172
  * Keys the runtime leaves unchanged (`prepareStringTransformation`): keys containing a
114
- * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter and keys ending
115
- * with a separator.
173
+ * character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
174
+ * a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
175
+ * keys that the other rules do not preserve are compared with the UUID form.
116
176
  */
117
- type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : false : true;
177
+ type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
118
178
  /**
119
179
  * The case the runtime detects for a key that is not preserved (`detectStringCase`), or
120
180
  * `'none'` when it cannot be detected (the key is then left unchanged). A string is used
@@ -125,25 +185,21 @@ type DetectedCase<K extends string> = K extends `${string}${Separator}${string}`
125
185
  type FirstChar<S extends string> = S extends `${infer C}${string}` ? C : '';
126
186
  /**
127
187
  * Whether a new word starts at `C`, followed by `Rest` (`isWordBoundary`): `C` is
128
- * uppercase and follows a lowercase letter or digit, or it ends an acronym that starts a
129
- * new word (two uppercase letters before it, a lowercase letter after it) — unless that
130
- * letter is a plural `s` not followed by another lowercase letter.
188
+ * uppercase and follows a lowercase letter or digit, or it follows an uppercase letter
189
+ * and comes before a lowercase letter, so the capitals before it are an acronym or a
190
+ * one-letter word — unless that lowercase letter is a plural `s` not followed by another
191
+ * lowercase letter.
131
192
  */
132
- type IsWordBoundary<C extends string, Prev extends string, PrevPrev extends string, Rest extends string> = C extends UpperLetter ? Prev extends LowerLetter | Digit ? true : Prev extends UpperLetter ? PrevPrev extends UpperLetter ? Rest extends `${infer Next}${infer AfterNext}` ? Next extends LowerLetter ? Next extends typeof ACRONYM_PLURAL_SUFFIX ? FirstChar<AfterNext> extends LowerLetter ? true : false : true : false : false : false : false : false;
193
+ type IsWordBoundary<C extends string, Prev extends string, Rest extends string> = C extends UpperLetter ? Prev extends LowerLetter | Digit ? true : Prev extends UpperLetter ? Rest extends `${infer Next}${infer AfterNext}` ? Next extends LowerLetter ? Next extends typeof ACRONYM_PLURAL_SUFFIX ? FirstChar<AfterNext> extends LowerLetter ? true : false : true : false : false : false : false;
133
194
  /** camelCase/PascalCase to snake_case or kebab-case, acronym-aware (`toSeparatedCase`) */
134
- type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends string = '', PrevPrev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? ToSeparatedCase<Rest, Sep, C, Prev, `${Acc}${IsWordBoundary<C, Prev, PrevPrev, Rest> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
135
- /**
136
- * Lowercases the first word, up to the first word boundary, so a leading acronym is
137
- * lowercased as a whole
138
- */
139
- type LowercaseFirstWord<S extends string, Prev extends string = '', PrevPrev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, PrevPrev, Rest> extends true ? `${Acc}${S}` : LowercaseFirstWord<Rest, C, Prev, `${Acc}${Lowercase<C>}`> : Acc;
195
+ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? ToSeparatedCase<Rest, Sep, C, `${Acc}${IsWordBoundary<C, Prev, Rest> extends true ? Sep : ''}${Lowercase<C>}`> : Acc;
140
196
  /**
141
- * PascalCase to camelCase (`pascalToCamelCase`): two capitals followed by a lowercase
142
- * letter start with a one-letter word (`XAxis` -> `xAxis`), unless that letter is a
143
- * plural `s` ending the acronym (`IDs` -> `ids`); otherwise the first word is lowercased
144
- * as a whole (`HTTPRequest` -> `httpRequest`)
197
+ * PascalCase to camelCase (`pascalToCamelCase`): lowercases the first word, up to the
198
+ * first word boundary, so a leading acronym is lowercased as a whole
199
+ * (`HTTPRequest` -> `httpRequest`) and a leading one-letter word on its own
200
+ * (`XAxis` -> `xAxis`)
145
201
  */
146
- type PascalToCamelCase<S extends string> = S extends `${infer A}${infer B}${infer C}${infer Rest}` ? [A, B, C] extends [UpperLetter, UpperLetter, LowerLetter] ? C extends typeof ACRONYM_PLURAL_SUFFIX ? FirstChar<Rest> extends LowerLetter ? Uncapitalize<S> : LowercaseFirstWord<S> : Uncapitalize<S> : LowercaseFirstWord<S> : LowercaseFirstWord<S>;
202
+ type PascalToCamelCase<S extends string, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, Rest> extends true ? `${Acc}${S}` : PascalToCamelCase<Rest, C, `${Acc}${Lowercase<C>}`> : Acc;
147
203
  /** snake_case/kebab-case to camelCase (`snakeToCamelCase`, `kebabToCamelCase`) */
148
204
  type SeparatedToCamelCase<S extends string, Sep extends Separator, Acc extends string = ''> = S extends `${infer Word}${Sep}${infer Next}${infer Rest}` ? SeparatedToCamelCase<Rest, Sep, `${Acc}${Word}${Uppercase<Next>}`> : `${Acc}${S}`;
149
205
  /** Replaces every `From` separator with `To` (`snakeToKebabCase`, `kebabToSnakeCase`) */
@@ -203,6 +259,47 @@ type TransformValue<T, To extends StringCase, Depth extends number | undefined,
203
259
  } : 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 ? {
204
260
  [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>>;
205
261
  } : T;
262
+ /**
263
+ * `TransformValue` while `exclude` paths still match: `Paths` is the rest of each of them
264
+ * below this level (see `excludeTypes.ts`). A key at which a path ends keeps its name,
265
+ * and the paths that continue below a key go on into its value. Arrays and Sets pass
266
+ * them on to their items, which add no key to a path. Values that no path reaches are
267
+ * transformed by `TransformValue`.
268
+ *
269
+ * Use `TransformResult` instead. This type is exported for the same reason as
270
+ * `TransformValue`.
271
+ */
272
+ 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>[] : {
273
+ [I in keyof T]: TransformValueExcluding<T[I], To, Depth, Level, Paths>;
274
+ } : 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 ? {
275
+ [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)>>;
276
+ } : T;
277
+ /**
278
+ * A key of an object, or a member of a Map's key type: it keeps its name where a path
279
+ * of `Ends` ends at it, and is transformed otherwise. A key type that is not a single
280
+ * literal keeps its name only where `*` ends.
281
+ */
282
+ 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;
283
+ /** `T` with the paths `Paths` that go on into it: by `TransformValue` once none is left */
284
+ 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>;
285
+ /**
286
+ * The value `T` under the key `Key`. A key type that is not a single literal may stand
287
+ * for several keys, which the paths may treat differently: each gives its own result.
288
+ */
289
+ type TransformBelowKey<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string, Key extends string> = IsStringLiteral<Key> extends true ? TransformBelow<T, To, Depth, Level, PathsBelow<Paths, Key>> : TransformBelowEach<T, To, Depth, Level, Paths, KeyCases<Paths, Key>>;
290
+ /** `T` under each of `Keys` (distributes over them) */
291
+ type TransformBelowEach<T, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Paths extends string, Keys extends string> = Keys extends unknown ? TransformBelow<T, To, Depth, Level, PathsBelow<Paths, Keys>> : never;
292
+ /**
293
+ * The paths below the keys of a Map with key type `K`, one tuple per set of them: a Map
294
+ * has one value type for all of its keys, while the paths below each key may differ.
295
+ * Only `*` matches a key that is not a string, and a key type that may also hold strings
296
+ * (`unknown`, `{}`) stands for every string key as well.
297
+ */
298
+ type MapPathsBelow<K, Paths extends string> = K extends string ? IsStringLiteral<K> extends true ? [PathsBelow<Paths, KeyName<K>>] : EachPathsBelow<Paths, KeyCases<Paths, K>> : string extends K ? EachPathsBelow<Paths, KeyCases<Paths, string>> : [PathsBelow<Paths, '*'>];
299
+ /** The paths below each of `Keys`, as a tuple each (distributes over them) */
300
+ type EachPathsBelow<Paths extends string, Keys extends string> = Keys extends unknown ? [PathsBelow<Paths, Keys>] : never;
301
+ /** The value type of a Map, below each set of paths of `Below` (see `MapPathsBelow`) */
302
+ type TransformBelowMapKeys<V, To extends StringCase, Depth extends number | undefined, Level extends unknown[], Below> = Below extends [infer Paths extends string] ? TransformBelow<V, To, Depth, Level, Paths> : never;
206
303
  /**
207
304
  * Applies a numeric literal `Depth`: a negative depth transforms nothing, and a
208
305
  * fractional depth is not supported precisely, so its result is `unknown`.
@@ -219,23 +316,60 @@ type IsNumberLiteral<N extends number> = [N & 0] extends [never] ? true : [N & 1
219
316
  * `undefined`, `unknown` for a depth that is only known to be a `number`.
220
317
  */
221
318
  type TransformToDepth<T, To extends StringCase, Depth extends number | undefined> = Depth extends unknown ? [Depth] extends [undefined] ? TransformValue<T, To, undefined, []> : IsNumberLiteral<Depth & number> extends true ? TransformWithDepth<T, To, Depth & number> : unknown : never;
319
+ /**
320
+ * `TransformToDepth` (and `TransformWithDepth`) for the `exclude` paths `Paths`. Kept
321
+ * apart, so that results without `exclude` take no extra step.
322
+ */
323
+ 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;
324
+ /**
325
+ * The result for an `exclude` tuple, collecting its paths into `Paths`. A union element
326
+ * stands for several tuples, which give their own results. A path with an empty key makes
327
+ * `transform` throw (`never`), and paths that are not all known (`string[]`, a rest
328
+ * element, a `string` element) give `unknown`.
329
+ */
330
+ type TransformExcluding<T, To extends StringCase, Depth extends number | undefined, Exclude extends readonly string[], Paths extends string = never> = Exclude extends readonly [] ? [Paths] extends [never] ? TransformToDepth<T, To, Depth> : TransformToDepthExcluding<T, To, Depth, Paths> : Exclude extends readonly [
331
+ infer Path extends string,
332
+ ...infer Rest extends readonly string[]
333
+ ] ? Path extends unknown ? IsStringLiteral<Path> extends true ? Path extends '' | `.${string}` | `${string}.` | `${string}..${string}` ? never : TransformExcluding<T, To, Depth, Rest, Paths | Path> : unknown : never : unknown;
334
+ /**
335
+ * Includes `true` when a member of `Exclude` is an array that is not a tuple (`string[]`,
336
+ * or a tuple with a rest element), or when `Exclude` is `any`: paths that cannot be known.
337
+ * Their result is `unknown`, and so is the result of a union that includes them, so
338
+ * checking this first saves working out the other members.
339
+ */
340
+ type HasUnknownPaths<Exclude> = Exclude extends readonly string[] ? number extends Exclude['length'] ? true : false : false;
341
+ /**
342
+ * The result for each member of `Exclude` (distributes over them): no path for an
343
+ * `undefined` one.
344
+ */
345
+ type TransformExcludingEach<T, To extends StringCase, Depth extends number | undefined, Exclude extends readonly string[] | undefined> = true extends HasUnknownPaths<Exclude> ? unknown : Exclude extends readonly string[] ? TransformExcluding<T, To, Depth, Exclude> : TransformToDepth<T, To, Depth>;
222
346
  /**
223
347
  * The exact type `transform` returns for `data` of type `T`, transformed to `To` (default
224
- * `'camel'`) for `Depth` nesting levels (default: all of them).
348
+ * `'camel'`) for `Depth` nesting levels (default: all of them), with the keys at the
349
+ * `Exclude` paths left unchanged (the type of the `exclude` option; default: none).
225
350
  *
226
- * `To` and `Depth` distribute over unions: when they are only known to be one of several
227
- * values, the result is the union of the possible results. A depth that is not a numeric
228
- * literal (`number`, including `Infinity`) cannot be known, so the result is `unknown`,
229
- * as it is for a `To` or `Depth` typed `any`. String enum members and branded literals
351
+ * `To`, `Depth` and `Exclude` distribute over unions: when they are only known to be one
352
+ * of several values, the result is the union of the possible results. A depth that is not
353
+ * a numeric literal (`number`, including `Infinity`) cannot be known, so the result is
354
+ * `unknown`, as it is for paths that are not a tuple of string literals (`string[]`), and
355
+ * for a `To`, `Depth` or `Exclude` typed `any`. String enum members and branded literals
230
356
  * are accepted for `To`.
231
357
  *
232
358
  * @example
233
359
  * ```typescript
234
360
  * type User = TransformResult<{ user_id: number; created_at: Date }>;
235
361
  * // => { userId: number; createdAt: Date }
362
+ *
363
+ * type Page = TransformResult<
364
+ * { page_items: Record<string, { item_id: number }> },
365
+ * 'camel',
366
+ * undefined,
367
+ * ['page_items.*']
368
+ * >;
369
+ * // => { pageItems: { [x: string]: { itemId: number } } }
236
370
  * ```
237
371
  */
238
- type TransformResult<T, To extends StringCase = 'camel', Depth extends number | undefined = undefined> = IsAny<To> extends true ? unknown : IsAny<Depth> extends true ? unknown : To extends unknown ? TransformToDepth<T, PlainCase<To>, Depth> : never;
372
+ type TransformResult<T, To extends StringCase = 'camel', Depth extends number | undefined = undefined, Exclude extends readonly string[] | undefined = undefined> = IsAny<To | Depth | Exclude> extends true ? unknown : [Exclude] extends [undefined] ? To extends unknown ? TransformToDepth<T, PlainCase<To>, Depth> : never : To extends unknown ? TransformExcludingEach<T, PlainCase<To>, Depth, Exclude> : never;
239
373
  /** `true` when `Options` may omit `Key` (an optional property) */
240
374
  type IsOptional<Options, Key extends PropertyKey> = Key extends keyof Options ? Record<never, never> extends Pick<Options, Key> ? true : false : false;
241
375
  /** `true` when the option `Key` of `Options` is typed `any` */
@@ -246,17 +380,29 @@ type IsAnyOption<Options, Key extends PropertyKey> = Key extends keyof Options ?
246
380
  */
247
381
  type HasAnyTargetCase<Options> = Options extends unknown ? IsAnyOption<Options, 'targetCase'> : never;
248
382
  /**
249
- * The result for one options object. An optional `targetCase` or `depth` may also be
250
- * omitted, which uses its default; those results are added explicitly because, without
251
- * `strictNullChecks`, `undefined` disappears from the property's type.
383
+ * The result for one options object, with the `exclude` paths `Exclude`. An optional
384
+ * `targetCase` or `depth` may also be omitted, which uses its default; those results are
385
+ * added explicitly because, without `strictNullChecks`, `undefined` disappears from the
386
+ * property's type.
252
387
  */
253
- type TransformResultForOne<T, Options, To extends StringCase = TargetCaseOf<Options>, Depth extends number | undefined = DepthOf<Options> extends infer D extends number | undefined ? D : never> = TransformResult<T, To, Depth> | (IsOptional<Options, 'targetCase'> extends true ? TransformResult<T, 'camel', Depth> : never) | (IsOptional<Options, 'depth'> extends true ? TransformResult<T, To, undefined> : never) | (IsOptional<Options, 'targetCase'> extends true ? IsOptional<Options, 'depth'> extends true ? TransformResult<T, 'camel', undefined> : never : never);
388
+ type TransformResultForOne<T, Options, Exclude extends readonly string[] | undefined = undefined, To extends StringCase = TargetCaseOf<Options>, Depth extends number | undefined = DepthOf<Options> extends infer D extends number | undefined ? D : never> = TransformResult<T, To, Depth, Exclude> | (IsOptional<Options, 'targetCase'> extends true ? TransformResult<T, 'camel', Depth, Exclude> : never) | (IsOptional<Options, 'depth'> extends true ? TransformResult<T, To, undefined, Exclude> : never) | (IsOptional<Options, 'targetCase'> extends true ? IsOptional<Options, 'depth'> extends true ? TransformResult<T, 'camel', undefined, Exclude> : never : never);
389
+ /**
390
+ * The result for one options object with an `exclude` option, which may also be omitted
391
+ * when it is optional (see `TransformResultForOne`). Options whose paths cannot be known,
392
+ * such as `TransformOptions` itself, give `unknown` straight away.
393
+ *
394
+ * Use `TransformResult` instead. This type is exported only so that declaration files
395
+ * can refer to it, as they do to `TransformResultFor`: the result of a generic wrapper
396
+ * whose `exclude` paths are a type parameter is written in terms of it.
397
+ */
398
+ type TransformResultForExcluding<T, Options> = ExcludeOf<Options> extends infer Exclude extends readonly string[] | undefined ? true extends HasUnknownPaths<Exclude> ? unknown : TransformResultForOne<T, Options, Exclude> | (IsOptional<Options, 'exclude'> extends true ? TransformResultForOne<T, Options> : never) : never;
254
399
  /**
255
400
  * The result type for an options object. Each member of a union of options objects gives
256
- * its own result, so its `targetCase` and `depth` stay paired. Options typed `any`, or
257
- * with a `targetCase` or `depth` typed `any`, say nothing about the result: `unknown`.
401
+ * its own result, so its `targetCase`, `depth` and `exclude` stay paired. Options typed
402
+ * `any`, or with a `targetCase`, `depth` or `exclude` typed `any`, say nothing about the
403
+ * result: `unknown`.
258
404
  */
259
- type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : TransformResultForOne<T, Options> : never;
405
+ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Options extends unknown ? IsAnyOption<Options, 'targetCase'> extends true ? unknown : IsAnyOption<Options, 'depth'> extends true ? unknown : 'exclude' extends keyof Options ? TransformResultForExcluding<T, Options> : TransformResultForOne<T, Options> : never;
260
406
 
261
407
  /**
262
408
  * Transforms the keys of `data` to camelCase, at every nesting level.
@@ -268,7 +414,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
268
414
  declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
269
415
  /**
270
416
  * Transforms the keys of `data` to `targetCase` (default `'camel'`), at every nesting
271
- * level unless `depth` is set.
417
+ * level unless `depth` is set, except the keys at the `exclude` paths.
272
418
  *
273
419
  * Every key of the result type is typed the way the runtime transforms it, except in
274
420
  * values TypeScript cannot tell apart from plain objects (see "Result types" in the
@@ -281,12 +427,22 @@ declare function transform<T>(data: T, options?: undefined): TransformResult<T>;
281
427
  * option names. An options object that may itself be `undefined` matches neither this
282
428
  * overload nor the one above, and gets an `unknown` result type from the last one.
283
429
  *
430
+ * The constraint of `Options` makes it capture an inline `exclude` array as a tuple of
431
+ * string literals: `[...Paths]` keeps the strings literal, and `readonly [string?]`
432
+ * makes TypeScript 4.7 read the array as a tuple, an empty one included; any array of
433
+ * strings is still accepted, and so is `undefined`: with `exactOptionalPropertyTypes`, an
434
+ * optional property only accepts it when its type includes it. The parameter type does
435
+ * not mention `exclude` itself: intersecting the captured tuple with these types would
436
+ * make every call compare array types member by member.
437
+ *
284
438
  * The type parameter defaults only apply when type arguments are passed explicitly, and
285
439
  * give an `unknown` result type. An explicit `any` as the second type argument was the
286
440
  * output type before `To` existed, so it still gives `any` (`To` is `any` there, while
287
441
  * the options keep their default type); a `targetCase` typed `any` gives `unknown`.
288
442
  */
289
- declare function transform<T, To extends StringCase | undefined = StringCase | undefined, Depth extends number | undefined = number | undefined, Options extends TransformOptions = TransformOptions>(data: T, options: Options & {
443
+ declare function transform<T, To extends StringCase | undefined = StringCase | undefined, Depth extends number | undefined = number | undefined, Options extends TransformOptions & {
444
+ exclude?: readonly [...Paths] | readonly [string?] | readonly string[] | undefined;
445
+ } = TransformOptions, Paths extends readonly string[] = readonly string[]>(data: T, options: Options & {
290
446
  targetCase?: To;
291
447
  depth?: Depth;
292
448
  } & NoUnknownOptions<Options>): IsAny<To> extends true ? true extends HasAnyTargetCase<Options> ? unknown : To : TransformResultFor<T, Options>;
@@ -294,12 +450,14 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
294
450
  * The 2.x form with explicit `<Data, SourceCase, TargetCase>` type arguments.
295
451
  *
296
452
  * @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
297
- * and the result type is inferred, `depth` included. This form does not accept `depth`.
453
+ * and the result type is inferred, `depth` and `exclude` included. This form does not
454
+ * accept `depth` or `exclude`.
298
455
  */
299
456
  declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
300
457
  sourceCase: From;
301
458
  targetCase: To;
302
459
  depth?: undefined;
460
+ exclude?: undefined;
303
461
  }): TransformResult<T, To>;
304
462
  /**
305
463
  * Transform data to a specified output type.
@@ -310,4 +468,4 @@ declare function transform<T, From extends StringCase, To extends StringCase>(da
310
468
  declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sourceCase'>): K;
311
469
 
312
470
  export { transform };
313
- export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformValue };
471
+ export type { StringCase, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };