@fluidframework/core-interfaces 2.23.0 → 2.30.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.
Files changed (71) hide show
  1. package/.eslintrc.cjs +19 -1
  2. package/CHANGELOG.md +4 -0
  3. package/api-extractor/api-extractor-lint-bundle.json +1 -1
  4. package/dist/exposedInternalUtilityTypes.d.ts +547 -0
  5. package/dist/exposedInternalUtilityTypes.d.ts.map +1 -0
  6. package/dist/exposedInternalUtilityTypes.js +11 -0
  7. package/dist/exposedInternalUtilityTypes.js.map +1 -0
  8. package/dist/exposedUtilityTypes.d.ts +10 -0
  9. package/dist/exposedUtilityTypes.d.ts.map +1 -0
  10. package/dist/exposedUtilityTypes.js +7 -0
  11. package/dist/exposedUtilityTypes.js.map +1 -0
  12. package/dist/internal.d.ts +34 -0
  13. package/dist/internal.d.ts.map +1 -0
  14. package/dist/internal.js +23 -0
  15. package/dist/internal.js.map +1 -0
  16. package/dist/jsonDeserialized.d.ts +112 -0
  17. package/dist/jsonDeserialized.d.ts.map +1 -0
  18. package/dist/jsonDeserialized.js +7 -0
  19. package/dist/jsonDeserialized.js.map +1 -0
  20. package/dist/jsonSerializable.d.ts +126 -0
  21. package/dist/jsonSerializable.d.ts.map +1 -0
  22. package/dist/jsonSerializable.js +7 -0
  23. package/dist/jsonSerializable.js.map +1 -0
  24. package/dist/jsonSerializationErrors.d.ts +31 -0
  25. package/dist/jsonSerializationErrors.d.ts.map +1 -0
  26. package/dist/jsonSerializationErrors.js +7 -0
  27. package/dist/jsonSerializationErrors.js.map +1 -0
  28. package/dist/jsonType.d.ts +30 -0
  29. package/dist/jsonType.d.ts.map +1 -0
  30. package/dist/jsonType.js +7 -0
  31. package/dist/jsonType.js.map +1 -0
  32. package/dist/package.json +16 -1
  33. package/internal/exposedUtilityTypes.d.ts +6 -0
  34. package/internal.d.ts +1 -6
  35. package/lib/exposedInternalUtilityTypes.d.ts +547 -0
  36. package/lib/exposedInternalUtilityTypes.d.ts.map +1 -0
  37. package/lib/exposedInternalUtilityTypes.js +10 -0
  38. package/lib/exposedInternalUtilityTypes.js.map +1 -0
  39. package/lib/exposedUtilityTypes.d.ts +10 -0
  40. package/lib/exposedUtilityTypes.d.ts.map +1 -0
  41. package/lib/exposedUtilityTypes.js +6 -0
  42. package/lib/exposedUtilityTypes.js.map +1 -0
  43. package/lib/internal.d.ts +34 -0
  44. package/lib/internal.d.ts.map +1 -0
  45. package/lib/internal.js +7 -0
  46. package/lib/internal.js.map +1 -0
  47. package/lib/jsonDeserialized.d.ts +112 -0
  48. package/lib/jsonDeserialized.d.ts.map +1 -0
  49. package/lib/jsonDeserialized.js +6 -0
  50. package/lib/jsonDeserialized.js.map +1 -0
  51. package/lib/jsonSerializable.d.ts +126 -0
  52. package/lib/jsonSerializable.d.ts.map +1 -0
  53. package/lib/jsonSerializable.js +6 -0
  54. package/lib/jsonSerializable.js.map +1 -0
  55. package/lib/jsonSerializationErrors.d.ts +31 -0
  56. package/lib/jsonSerializationErrors.d.ts.map +1 -0
  57. package/lib/jsonSerializationErrors.js +6 -0
  58. package/lib/jsonSerializationErrors.js.map +1 -0
  59. package/lib/jsonType.d.ts +30 -0
  60. package/lib/jsonType.d.ts.map +1 -0
  61. package/lib/jsonType.js +6 -0
  62. package/lib/jsonType.js.map +1 -0
  63. package/package.json +50 -7
  64. package/src/cjs/package.json +19 -0
  65. package/src/exposedInternalUtilityTypes.ts +1152 -0
  66. package/src/exposedUtilityTypes.ts +20 -0
  67. package/src/internal.ts +73 -0
  68. package/src/jsonDeserialized.ts +118 -0
  69. package/src/jsonSerializable.ts +133 -0
  70. package/src/jsonSerializationErrors.ts +34 -0
  71. package/src/jsonType.ts +37 -0
@@ -0,0 +1,1152 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /* eslint-disable @rushstack/no-new-null */
7
+
8
+ import type {
9
+ SerializationErrorPerNonPublicProperties,
10
+ SerializationErrorPerUndefinedArrayElement,
11
+ } from "./jsonSerializationErrors.js";
12
+ import type { JsonTypeWith, NonNullJsonObjectWith } from "./jsonType.js";
13
+
14
+ /**
15
+ * Unique symbol for recursion meta-typing.
16
+ */
17
+ const RecursionMarkerSymbol: unique symbol = Symbol("recursion here");
18
+
19
+ /**
20
+ * Collection of utility types that are not intended to be used/imported
21
+ * directly outside of this package.
22
+ *
23
+ * @privateRemarks
24
+ * There are ony three intentional exports from this module:
25
+ * - {@link InternalUtilityTypes.IfSameType | IfSameType}
26
+ * - {@link InternalUtilityTypes.JsonDeserializedImpl | JsonDeserializedImpl }
27
+ * - {@link InternalUtilityTypes.JsonSerializableImpl | JsonSerializableImpl }
28
+ *
29
+ * api-extractor will allow `export` to be removed from others but generates
30
+ * api-report a little oddly with a rogue `{};` floating at end of namespace
31
+ * in api.md file. It will promote all of the support types to appear as
32
+ * exported anyway. All in namespace are left exported to avoid api-extractor
33
+ * potentially failing to validate other modules correctly.
34
+ *
35
+ * @beta
36
+ * @system
37
+ */
38
+ // eslint-disable-next-line @typescript-eslint/no-namespace
39
+ export namespace InternalUtilityTypes {
40
+ /**
41
+ * Meta-type for controlling filtering utilities.
42
+ *
43
+ * @system
44
+ */
45
+ export interface FilterControls {
46
+ /**
47
+ * Tuple of exact types that are managed by custom serialization/deserialization
48
+ * logic (beyond JSON.stringify and JSON.parse without replacers/revivers).
49
+ * Only exact types matching specification will be preserved unaltered.
50
+ */
51
+ AllowExactly: unknown[];
52
+
53
+ /**
54
+ * General types that are managed by custom serialization/deserialization
55
+ * logic (beyond JSON.stringify and JSON.parse without replacers/revivers).
56
+ * Any type satisfying specification will be preserved unaltered.
57
+ */
58
+ AllowExtensionOf: unknown;
59
+ }
60
+
61
+ /**
62
+ * Meta-type for controlling filtering utilities that additionally supplies
63
+ * a substitute type for degenerate cases.
64
+ *
65
+ * @system
66
+ */
67
+ interface FilterControlsWithSubstitution extends FilterControls {
68
+ /**
69
+ * Type to use for degenerate cases like `unknown` or `any`.
70
+ * Typically this will be `JsonTypeWith<TupleToUnion<AllowExactly> | AllowExtensionOf>`.
71
+ */
72
+ DegenerateSubstitute: unknown;
73
+ }
74
+
75
+ /**
76
+ * Meta-type for controlling deserialized filtering utilities.
77
+ *
78
+ * @system
79
+ */
80
+ interface DeserializedFilterControls extends FilterControlsWithSubstitution {
81
+ /**
82
+ * Type to use for degenerate `object` case.
83
+ * Typically this will be `NonNullJsonObjectWith<TupleToUnion<AllowExactly> | AllowExtensionOf>`.
84
+ */
85
+ DegenerateNonNullObjectSubstitute: unknown;
86
+ }
87
+
88
+ /**
89
+ * Returns non-symbol keys for optional properties of an object type.
90
+ * This excludes indexed properties that are inherently _optional_.
91
+ *
92
+ * For homomorphic mapping use with `as` to filter. Example:
93
+ * `[K in keyof T as OptionalNonSymbolKeysOf<T, K>]: ...`
94
+ *
95
+ * @system
96
+ */
97
+ export type OptionalNonSymbolKeysOf<
98
+ T extends object,
99
+ Keys extends keyof T = keyof T,
100
+ > = Exclude<
101
+ {
102
+ [K in Keys]: T extends Record<K, T[K]> ? never : K;
103
+ }[Keys],
104
+ undefined | symbol
105
+ >;
106
+
107
+ /**
108
+ * Returns non-symbol keys for required properties of an object type.
109
+ * This includes indexed properties that are inherently _optional_.
110
+ *
111
+ * For homomorphic mapping use with `as` to filter. Example:
112
+ * `[K in keyof T as RequiredNonSymbolKeysOf<T, K>]: ...`
113
+ *
114
+ * @system
115
+ */
116
+ export type RequiredNonSymbolKeysOf<
117
+ T extends object,
118
+ Keys extends keyof T = keyof T,
119
+ > = Exclude<
120
+ {
121
+ [K in Keys]: T extends Record<K, T[K]> ? K : never;
122
+ }[Keys],
123
+ undefined | symbol
124
+ >;
125
+
126
+ /**
127
+ * Returns Result.WhenSomethingDeserializable if T is sometimes at least a
128
+ * partially deserializable type, otherwise Result.WhenNeverDeserializable.
129
+ * Fully not deserializable (bigints, symbols, undefined and functions without
130
+ * other properties less overlap with T*Exception) produce Result.WhenNeverDeserializable.
131
+ * An object would have a defined result even if parts of its content are
132
+ * not deserializable.
133
+ *
134
+ * @param Result - Result type with two properties. One property must always
135
+ * be `never` as `T` maybe a union of never deserializable and at least
136
+ * partially deserializable types and the result is a union of Result.*.
137
+ *
138
+ * @privateRemarks
139
+ * If `Result.WhenSomethingDeserializable` was `true` and
140
+ * `Result.WhenNeverDeserializable` was `false`, then the return type
141
+ * for type `T` would be `boolean` for a sometimes deserializable type.
142
+ *
143
+ * @system
144
+ */
145
+ export type TestDeserializabilityOf<
146
+ T,
147
+ TExactExceptions extends unknown[],
148
+ TExtendsException,
149
+ Result extends
150
+ | { WhenSomethingDeserializable: unknown; WhenNeverDeserializable: never }
151
+ | { WhenSomethingDeserializable: never; WhenNeverDeserializable: unknown },
152
+ > = /* ensure working with more than never */ T extends never
153
+ ? /* never => */ Result["WhenNeverDeserializable"]
154
+ : /* check for extends exception */ T extends TExtendsException
155
+ ? /* extends exception => */ Result["WhenSomethingDeserializable"]
156
+ : /* no extends exception => check for exact exception */ IfExactTypeInTuple<
157
+ T,
158
+ TExactExceptions,
159
+ /* exact exception => */ Result["WhenSomethingDeserializable"],
160
+ /* no exception => check for only non-serializable value types */ T extends
161
+ | bigint
162
+ | symbol
163
+ | undefined
164
+ ? /* not serializable => */ Result["WhenNeverDeserializable"]
165
+ : // eslint-disable-next-line @typescript-eslint/ban-types
166
+ T extends Function
167
+ ? ExtractFunctionFromIntersection<T> extends {
168
+ classification: "exactly Function";
169
+ }
170
+ ? /* not serializable => */ Result["WhenNeverDeserializable"]
171
+ : /* at least partially serializable */ Result["WhenSomethingDeserializable"]
172
+ : /* at least partially serializable */ Result["WhenSomethingDeserializable"]
173
+ >;
174
+
175
+ /**
176
+ * Similar to `Exclude` but only excludes exact `U`s from `T`
177
+ * rather than any type that extends `U`.
178
+ *
179
+ * @system
180
+ */
181
+ export type ExcludeExactly<T, U> = IfSameType<T, U, never, T>;
182
+
183
+ /**
184
+ * Similar to `Exclude` but only excludes exact members of `U` from `T`
185
+ * rather than any type that extends members of `U`.
186
+ *
187
+ * @system
188
+ */
189
+ export type ExcludeExactlyInTuple<T, TupleOfU extends unknown[]> = IfExactTypeInTuple<
190
+ T,
191
+ TupleOfU,
192
+ never,
193
+ T
194
+ >;
195
+
196
+ /**
197
+ * Similar to `Omit` but operates on tuples.
198
+ * Removes elements of `Tuple` that extend `U`.
199
+ *
200
+ * @system
201
+ */
202
+ export type OmitFromTuple<
203
+ Tuple extends unknown[],
204
+ U,
205
+ Accumulated extends unknown[] = [],
206
+ > = Tuple extends [infer First, ...infer Rest]
207
+ ? OmitFromTuple<Rest, U, First extends U ? Accumulated : [...Accumulated, First]>
208
+ : Accumulated;
209
+
210
+ /**
211
+ * Similar to `OmitFromTuple` but removes only exact matches of U.
212
+ * Removes elements of `Tuple` that are exactly `U`.
213
+ *
214
+ * @remarks If `U` is a union, then only exactly matching union elements of `Tuple` are removed.
215
+ * @system
216
+ */
217
+ export type OmitExactlyFromTuple<
218
+ Tuple extends unknown[],
219
+ U,
220
+ Accumulated extends unknown[] = [],
221
+ > = Tuple extends [infer First, ...infer Rest]
222
+ ? OmitExactlyFromTuple<Rest, U, IfSameType<First, U, Accumulated, [...Accumulated, First]>>
223
+ : Accumulated;
224
+
225
+ /**
226
+ * Returns non-symbol keys for defined, (likely) serializable properties of an
227
+ * object type. Keys with fully unsupported properties (undefined, symbol, and
228
+ * bigint) and sometimes unsupported (functions) are excluded. An exception to
229
+ * that is when there are supported types in union with just bigint.
230
+ *
231
+ * For homomorphic mapping use with `as` to filter. Example:
232
+ * `[K in keyof T as NonSymbolWithDeserializablePropertyOf<T, [], never, K>]: ...`
233
+ *
234
+ * @system
235
+ */
236
+ export type NonSymbolWithDeserializablePropertyOf<
237
+ T extends object,
238
+ TExactExceptions extends unknown[],
239
+ TExtendsException,
240
+ Keys extends keyof T = keyof T,
241
+ > = Exclude<
242
+ {
243
+ [K in Keys]: /* all possible types that aren't already allowed, with the exception of `unknown` */
244
+ ExcludeExactlyInTuple<
245
+ Exclude<T[K], TExtendsException>,
246
+ OmitExactlyFromTuple<TExactExceptions, unknown>
247
+ > extends infer PossibleTypeLessAllowed
248
+ ? IfSameType<
249
+ PossibleTypeLessAllowed,
250
+ unknown,
251
+ /* value might not be supported => exclude K */ never,
252
+ /* extract types that might lead to missing property */ Extract<
253
+ PossibleTypeLessAllowed,
254
+ /* types that might lead to missing property, except `bigint` */
255
+ // eslint-disable-next-line @typescript-eslint/ban-types
256
+ undefined | symbol | Function
257
+ > extends never
258
+ ? /* all types are supported plus possibly `bigint` => */
259
+ /* check for only `bigint` remaining */ IfSameType<
260
+ PossibleTypeLessAllowed,
261
+ bigint,
262
+ /* only `bigint` => nothing supported */ never,
263
+ /* exclusively supported types (and maybe `bigint`) or exactly `never` */
264
+ /* => check for `never` */ T[K] extends never ? never : K
265
+ >
266
+ : /* value might not be supported => exclude K */ never
267
+ >
268
+ : never;
269
+ }[Keys],
270
+ undefined | symbol
271
+ >;
272
+
273
+ /**
274
+ * Returns non-symbol keys for partially supported properties of an object type.
275
+ * Keys with only unsupported properties (undefined, symbol, bigint, and
276
+ * functions without other properties) are excluded.
277
+ *
278
+ * For homomorphic mapping use with `as` to filter. Example:
279
+ * `[K in keyof T as NonSymbolWithPossiblyDeserializablePropertyOf<T, [], never, K>]: ...`
280
+ *
281
+ * @system
282
+ */
283
+ export type NonSymbolWithPossiblyDeserializablePropertyOf<
284
+ T extends object,
285
+ TExactExceptions extends unknown[],
286
+ TExtendsException,
287
+ Keys extends keyof T = keyof T,
288
+ > = Exclude<
289
+ {
290
+ [K in Keys]: /* all possible types that aren't already allowed, with the exception of `unknown` */
291
+ ExcludeExactlyInTuple<
292
+ Exclude<T[K], TExtendsException>,
293
+ OmitExactlyFromTuple<TExactExceptions, unknown>
294
+ > extends infer PossibleTypeLessAllowed
295
+ ? Extract<
296
+ IfSameType<PossibleTypeLessAllowed, unknown, undefined, PossibleTypeLessAllowed>,
297
+ /* types that might lead to missing property */
298
+ // eslint-disable-next-line @typescript-eslint/ban-types
299
+ undefined | symbol | Function
300
+ > extends never
301
+ ? /* exclusively supported types or exactly `never` */ never
302
+ : /* at least some unsupported type => check for any supported */ TestDeserializabilityOf<
303
+ T[K],
304
+ OmitExactlyFromTuple<TExactExceptions, unknown>,
305
+ TExtendsException,
306
+ { WhenSomethingDeserializable: K; WhenNeverDeserializable: never }
307
+ >
308
+ : never;
309
+ }[Keys],
310
+ undefined | symbol
311
+ >;
312
+
313
+ /**
314
+ * Filters a type `T` for `undefined` that is not viable in an array (or tuple) that
315
+ * must go through JSON serialization.
316
+ * If `T` is `undefined`, then error type {@link SerializationErrorPerUndefinedArrayElement}
317
+ * is returned with hopes of being informative.
318
+ *
319
+ * @system
320
+ */
321
+ export type JsonForSerializableArrayItem<
322
+ T,
323
+ Controls extends FilterControls,
324
+ TAncestorTypes extends unknown[],
325
+ TBlessed,
326
+ > = /* Some initial filtering must be provided before a test for undefined. */
327
+ /* These tests are expected to match those in JsonSerializableImpl. */
328
+ /* test for 'any' */ boolean extends (T extends never ? true : false)
329
+ ? /* 'any' => */ TBlessed
330
+ : /* test for 'unknown' */ unknown extends T
331
+ ? /* 'unknown' => */ TBlessed
332
+ : /* test for exact recursion */ IfExactTypeInTuple<
333
+ T,
334
+ TAncestorTypes,
335
+ /* recursion; stop here => */ T,
336
+ /* test for JSON primitive types or given alternative */ T extends
337
+ | null
338
+ | boolean
339
+ | number
340
+ | string
341
+ | Controls["AllowExtensionOf"]
342
+ ? /* primitive types or alternative => */ T
343
+ : /* test for exact alternative */ IfExactTypeInTuple<
344
+ T,
345
+ Controls["AllowExactly"],
346
+ T,
347
+ /* test for undefined possibility */ undefined extends T
348
+ ? /* undefined | ... => */ SerializationErrorPerUndefinedArrayElement
349
+ : TBlessed
350
+ >
351
+ >;
352
+
353
+ /**
354
+ * Filters a type `T` for types that become null through JSON serialization.
355
+ *
356
+ * @system
357
+ */
358
+ export type JsonForDeserializedArrayItem<
359
+ T,
360
+ Controls extends DeserializedFilterControls,
361
+ TBlessed,
362
+ > = /* Some initial filtering must be provided before a test for undefined, symbol, or function. */
363
+ /* These tests are expected to match those in JsonDeserializedImpl. */
364
+ /* test for 'any' */ boolean extends (T extends never ? true : false)
365
+ ? /* 'any' => */ TBlessed
366
+ : /* test for 'unknown' */ unknown extends T
367
+ ? /* 'unknown' => */ TBlessed
368
+ : /* test for JSON primitive types or general alternative */ T extends
369
+ | null
370
+ | boolean
371
+ | number
372
+ | string
373
+ | Controls["AllowExtensionOf"]
374
+ ? /* primitive or replaced types => */ T
375
+ : /* test for exact alternative */ IfExactTypeInTuple<
376
+ T,
377
+ Controls["AllowExactly"],
378
+ /* exactly replaced => */ T,
379
+ /* test for known types that become null */ T extends undefined | symbol
380
+ ? /* => */ null
381
+ : // eslint-disable-next-line @typescript-eslint/ban-types
382
+ T extends Function
383
+ ? ExtractFunctionFromIntersection<T> extends {
384
+ classification: "exactly Function";
385
+ }
386
+ ? null
387
+ : null | TBlessed
388
+ : TBlessed
389
+ >;
390
+
391
+ /**
392
+ * Checks for a type that is simple class of number and string indexed types to numbers and strings.
393
+ *
394
+ * @system
395
+ */
396
+ export type IfEnumLike<
397
+ T extends object,
398
+ EnumLike = never,
399
+ NotEnumLike = unknown,
400
+ > = T extends readonly (infer _)[]
401
+ ? /* array => */ NotEnumLike
402
+ : // eslint-disable-next-line @typescript-eslint/ban-types
403
+ T extends Function
404
+ ? /* function => */ NotEnumLike
405
+ : T extends {
406
+ // all numerical indices should refer to a string
407
+ readonly [i: number]: string;
408
+ // string indices may be string or number
409
+ readonly [p: string]: number | string;
410
+ // no symbol indices are allowed
411
+ readonly [s: symbol]: never;
412
+ }
413
+ ? /* test for a never or any property */ true extends {
414
+ [K in keyof T]: T[K] extends never ? true : never;
415
+ }[keyof T]
416
+ ? NotEnumLike
417
+ : EnumLike
418
+ : NotEnumLike;
419
+
420
+ /**
421
+ * Test for type equality
422
+ *
423
+ * @returns IfSame if identical and IfDifferent otherwise.
424
+ *
425
+ * Implementation derived from https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
426
+ *
427
+ * @remarks Use caution when one of the type might be `{}`. That type is
428
+ * special and produces unexpected results. This includes variability
429
+ * on past usages.
430
+ *
431
+ * @system
432
+ */
433
+ export type IfSameType<X, Y, IfSame = unknown, IfDifferent = never> = (<T>() => T extends X
434
+ ? 1
435
+ : 2) extends <T>() => T extends Y ? 1 : 2
436
+ ? IfSame
437
+ : IfDifferent;
438
+
439
+ /**
440
+ * Test for type equality with tuple of other types.
441
+ *
442
+ * @typeParam T - Type to find in Tuple.
443
+ * @typeParam Tuple - Tuple of types to test against.
444
+ * @typeParam IfMatch - Type to return if match is found.
445
+ * @typeParam IfNoMatch - Type to return if no match is found.
446
+ *
447
+ * @privateRemarks
448
+ * Tests for an exact match of `T` in `Tuple[0]`. If not found,
449
+ * recurses with the remainder of the tuple.
450
+ */
451
+ export type IfExactTypeInTuple<
452
+ T,
453
+ Tuple extends unknown[],
454
+ IfMatch = unknown,
455
+ IfNoMatch = never,
456
+ > = Tuple extends [infer First, ...infer Rest]
457
+ ? IfSameType<T, First, IfMatch, IfExactTypeInTuple<T, Rest, IfMatch, IfNoMatch>>
458
+ : IfNoMatch;
459
+
460
+ /**
461
+ * Test for type equality with union of other types.
462
+ *
463
+ * @typeParam T - Type to find in Union. If this is itself a union, then all types must be found in Union.
464
+ * @typeParam Union - Union of types to test against.
465
+ * @typeParam IfMatch - Type to return if match is found.
466
+ * @typeParam IfNoMatch - Type to return if no match is found.
467
+ *
468
+ * @remarks
469
+ * In a recursive context, use {@link InternalUtilityTypes.IfExactTypeInTuple} to manage ancestry.
470
+ *
471
+ * @privateRemarks
472
+ * Perhaps it is a Typescript defect but a simple check that `T` is `never`
473
+ * via `T extends never` does not work as expected in this context.
474
+ * Workaround using `IfSameType<..., never,...>`.
475
+ * @system
476
+ */
477
+ export type IfExactTypeInUnion<T, Union, IfMatch = unknown, IfNoMatch = never> = IfSameType<
478
+ T,
479
+ never,
480
+ /* T is never => */ IfSameType<Union, never, IfMatch, IfNoMatch>,
481
+ /* T is NOT never => */ IfSameType<T, Extract<Union, T>, IfMatch, IfNoMatch>
482
+ >;
483
+
484
+ /**
485
+ * Test for type equality
486
+ *
487
+ * @returns `true` if identical and `false` otherwise.
488
+ *
489
+ * @remarks Use caution when one of the type might be `{}`. That type is
490
+ * special and produces unexpected results. This includes variability
491
+ * on past usages.
492
+ *
493
+ * @system
494
+ */
495
+ export type IsSameType<X, Y> = IfSameType<X, Y, true, false>;
496
+
497
+ /**
498
+ * Checks that type is exactly `object`.
499
+ *
500
+ * @system
501
+ */
502
+ export type IsExactlyObject<T extends object> = IsSameType<T, object>;
503
+
504
+ /**
505
+ * Creates a simple object type from an intersection of multiple.
506
+ * @privateRemarks
507
+ * `T extends Record` within the implementation encourages tsc to process
508
+ * intersections within unions.
509
+ *
510
+ * @system
511
+ */
512
+ export type FlattenIntersection<T extends Record<string | number | symbol, unknown>> =
513
+ T extends Record<string | number | symbol, unknown>
514
+ ? {
515
+ [K in keyof T]: T[K];
516
+ }
517
+ : T;
518
+
519
+ /**
520
+ * Extracts Function portion from an intersection (&) type returning
521
+ * the extracted portion in the `function` property or `unknown` if
522
+ * no function is found.
523
+ * The returned `classification` property has one of three values:
524
+ * - "no Function" if the type is not a function.
525
+ * - "exactly Function" if the type is exactly a function.
526
+ * - "Function and more" if the type is a function and has other properties.
527
+ *
528
+ * @system
529
+ */
530
+ export type ExtractFunctionFromIntersection<T extends object> = (T extends new (
531
+ ...args: infer A
532
+ ) => infer R
533
+ ? new (
534
+ ...args: A
535
+ ) => R
536
+ : unknown) &
537
+ (T extends (...args: infer A) => infer R
538
+ ? (...args: A) => R
539
+ : unknown) extends infer Functional
540
+ ? {
541
+ classification: unknown extends Functional
542
+ ? "no Function"
543
+ : Functional extends Required<T>
544
+ ? "exactly Function"
545
+ : "Function and more";
546
+ function: Functional;
547
+ }
548
+ : never;
549
+
550
+ /**
551
+ * Returns `Filtered` & any Function intersection from `Original`.
552
+ * If `Original` is exactly a Function, then `Filtered` is left out
553
+ * under the assumption that it is not useful/applicable.
554
+ *
555
+ * @system
556
+ */
557
+ export type FilterPreservingFunction<
558
+ Original extends object,
559
+ Filtered,
560
+ > = ExtractFunctionFromIntersection<Original> extends {
561
+ classification: infer TClassification;
562
+ function: infer TFunction;
563
+ }
564
+ ? TClassification extends "exactly Function"
565
+ ? TFunction
566
+ : TFunction & Filtered
567
+ : never;
568
+
569
+ /**
570
+ * Replaces any instance where a type T recurses into itself or a portion of
571
+ * itself with TRecursionMarker.
572
+ *
573
+ * @typeParam T - Type to process.
574
+ * @typeParam TRecursionMarker - Replacement marker type.
575
+ * @typeParam Controls - Allowances are preserved as-is.
576
+ * @typeParam TAncestorTypes - Tuple of types that are ancestors of T.
577
+ * @typeParam TNextAncestor - Set exactly to T. This is passed separately
578
+ * such that T union types remain intact as exact ancestors.
579
+ *
580
+ * @remarks
581
+ * Filtering applied to class instances with non-public properties will not
582
+ * preserve the class instance unless those classes are known and listed as
583
+ * allowances via `Controls`.
584
+ *
585
+ * @privateRemarks
586
+ * This implementation handles functions including function with properties.
587
+ * There are no known cases where replacing recursion under such types make
588
+ * a difference. Either the function (whole type) is allowed by the Json
589
+ * filters or function is not allowed at all.
590
+ * If the function portion is found to be problematic later, then could use
591
+ * `T extends Function ? T : ...` to ignore function objects.
592
+ *
593
+ * @system
594
+ */
595
+ export type ReplaceRecursionWithMarkerAndPreserveAllowances<
596
+ T,
597
+ TRecursionMarker,
598
+ Controls extends FilterControls,
599
+ TAncestorTypes extends unknown[] = [],
600
+ TNextAncestor = T,
601
+ > = /* test for recursion */
602
+ IfExactTypeInTuple<T, TAncestorTypes, true, "no match"> extends true
603
+ ? /* recursion => use replacement */ TRecursionMarker
604
+ : /* force union separation hereafter */ T extends infer _
605
+ ? /* test for recursion among union elements */
606
+ IfExactTypeInTuple<T, TAncestorTypes, true, "no match"> extends true
607
+ ? TRecursionMarker
608
+ : /* test for general allowance */ T extends Controls["AllowExtensionOf"]
609
+ ? /* allowed extension type => */ T
610
+ : /* test for exact allowance */ IfExactTypeInTuple<
611
+ T,
612
+ Controls["AllowExactly"],
613
+ true,
614
+ "no match"
615
+ > extends true
616
+ ? /* exact allowed type => */ T
617
+ : T extends object
618
+ ? FilterPreservingFunction<
619
+ T,
620
+ {
621
+ [K in keyof T]: ReplaceRecursionWithMarkerAndPreserveAllowances<
622
+ T[K],
623
+ TRecursionMarker,
624
+ Controls,
625
+ [TNextAncestor, ...TAncestorTypes]
626
+ >;
627
+ }
628
+ >
629
+ : /* non-object => T as is */ T
630
+ : never;
631
+
632
+ /**
633
+ * Replaces any instances of "allowed" types and recursion within with `never`.
634
+ *
635
+ * @typeParam T - Type to process.
636
+ * @typeParam Controls - Allowances to replace.
637
+ * @typeParam TAncestorTypes - Tuple of types that are ancestors of T.
638
+ * @typeParam TNextAncestor - Set exactly to T. This is passed separately
639
+ * such that T union types remain intact as exact ancestors.
640
+ *
641
+ * @system
642
+ */
643
+ export type ReplaceAllowancesAndRecursionWithNever<
644
+ T,
645
+ Controls extends FilterControls,
646
+ TAncestorTypes extends unknown[] = [],
647
+ TNextAncestor = T,
648
+ > = /* test for exact recursion first */ IfExactTypeInTuple<
649
+ T,
650
+ TAncestorTypes,
651
+ true,
652
+ "no match"
653
+ > extends true
654
+ ? /* recursion => */ never
655
+ : /* test for general allowance (also forces union separation) */ T extends Controls["AllowExtensionOf"]
656
+ ? /* allowed extension type => */ never
657
+ : /* test for exact allowance */ IfExactTypeInTuple<
658
+ T,
659
+ Controls["AllowExactly"],
660
+ true,
661
+ "no match"
662
+ > extends true
663
+ ? /* exact allowed type => */ never
664
+ : /* test for recursion among union elements */ IfExactTypeInTuple<
665
+ T,
666
+ TAncestorTypes,
667
+ true,
668
+ "no match"
669
+ > extends true
670
+ ? /* recursion => */ never
671
+ : T extends object
672
+ ? FilterPreservingFunction<
673
+ T,
674
+ {
675
+ [K in keyof T]: ReplaceAllowancesAndRecursionWithNever<
676
+ T[K],
677
+ Controls,
678
+ [TNextAncestor, ...TAncestorTypes]
679
+ >;
680
+ }
681
+ >
682
+ : /* non-object => T as is */ T;
683
+
684
+ /**
685
+ * Test for non-public properties (which can only exist on class instance types).
686
+ *
687
+ * Returns `HasNonPublic` if `T` deeply may contain a private or protected field
688
+ * and `OnlyPublics` otherwise.
689
+ *
690
+ * @remarks
691
+ * Compare original (unprocessed) to filtered case that has `never` where
692
+ * recursing or where allowed exception types are used.
693
+ *
694
+ * Note that this a test of the type and not the actual data. So, if an
695
+ * interface is given as `T` where implemented by a class, any private or
696
+ * protected fields within the class will not be detected.
697
+ *
698
+ * @system
699
+ */
700
+ export type IfNonPublicProperties<
701
+ T,
702
+ Controls extends FilterControls,
703
+ HasNonPublic = never,
704
+ OnlyPublics = unknown,
705
+ > = ReplaceAllowancesAndRecursionWithNever<T, Controls> extends T
706
+ ? OnlyPublics
707
+ : HasNonPublic;
708
+
709
+ /**
710
+ * Union of all types in a tuple.
711
+ *
712
+ * @system
713
+ */
714
+ export type TupleToUnion<T extends unknown[]> = T[number];
715
+
716
+ // #region JsonSerializable implementation
717
+
718
+ /**
719
+ * Outer implementation of {@link JsonSerializable} handling meta cases
720
+ * like classes (with non-public properties).
721
+ *
722
+ * @system
723
+ */
724
+ export type JsonSerializableImpl<
725
+ T,
726
+ Options extends Partial<FilterControls> & {
727
+ IgnoreInaccessibleMembers?: "ignore-inaccessible-members";
728
+ },
729
+ TAncestorTypes extends unknown[] = [],
730
+ TNextAncestor = T,
731
+ > = /* Build Controls from Options filling in defaults for any missing properties */
732
+ {
733
+ AllowExactly: Options extends { AllowExactly: unknown[] } ? Options["AllowExactly"] : [];
734
+ AllowExtensionOf: Options extends { AllowExtensionOf: unknown }
735
+ ? Options["AllowExtensionOf"]
736
+ : never;
737
+ // There Substitute type could be extracted to helper type, but are kept explicit here
738
+ // to make JsonTypeWith show explicitly in results for users, rather
739
+ // than either the helper type name or a partially unrolled version.
740
+ DegenerateSubstitute: JsonTypeWith<
741
+ | (Options extends { AllowExactly: unknown[] }
742
+ ? TupleToUnion<Options["AllowExactly"]>
743
+ : never)
744
+ | (Options extends { AllowExtensionOf: unknown } ? Options["AllowExtensionOf"] : never)
745
+ >;
746
+ } extends infer Controls
747
+ ? /* Controls should always satisfy FilterControlsWithSubstitution, but Typescript wants a check */
748
+ Controls extends FilterControlsWithSubstitution
749
+ ? /* test for 'any' */ boolean extends (T extends never ? true : false)
750
+ ? /* 'any' => */ Controls["DegenerateSubstitute"]
751
+ : Options["IgnoreInaccessibleMembers"] extends "ignore-inaccessible-members"
752
+ ? JsonSerializableFilter<T, Controls, TAncestorTypes, TNextAncestor>
753
+ : /* test for non-public properties (class instance type) */
754
+ IfNonPublicProperties<
755
+ T,
756
+ {
757
+ AllowExactly: Controls["AllowExactly"];
758
+ // Add in primitives that may be branded to ignore intersection classes
759
+ AllowExtensionOf: Controls["AllowExtensionOf"] | boolean | number | string;
760
+ DegenerateSubstitute: Controls["DegenerateSubstitute"];
761
+ },
762
+ "found non-publics",
763
+ "only publics"
764
+ > extends "found non-publics"
765
+ ? /* hidden props => test if it is array properties that are the problem */ T extends readonly (infer _)[]
766
+ ? /* array => */ {
767
+ /* use homomorphic mapped type to preserve tuple type */
768
+ [K in keyof T]: JsonSerializableImpl<
769
+ T[K],
770
+ Controls,
771
+ [TNextAncestor, ...TAncestorTypes]
772
+ >;
773
+ }
774
+ : /* test for potentially branded primitive (intersection with a supported primitive) */
775
+ T extends boolean | number | string
776
+ ? /* assume intersection is branding and allow as-is => */ T
777
+ : /* not array => error */ SerializationErrorPerNonPublicProperties
778
+ : /* no hidden properties => apply filtering => */ JsonSerializableFilter<
779
+ T,
780
+ Controls,
781
+ TAncestorTypes,
782
+ TNextAncestor
783
+ >
784
+ : never /* FilterControlsWithSubstitution assert else; should never be reached */
785
+ : never /* unreachable else for infer */;
786
+
787
+ /**
788
+ * Essentially a check for a template literal that has $\{string\} or
789
+ * $\{number\} in the pattern. Just `string` and/or `number` also match.
790
+ *
791
+ * @remarks This works recursively looking at first elements when not
792
+ * `string` or `number`. `first` will just be a single character if
793
+ * not $\{string\} or $\{number\}.
794
+ *
795
+ * @system
796
+ */
797
+ export type IfIndexKey<T, IfIndex, IfLiteral> = `${string}` extends T
798
+ ? IfIndex
799
+ : number extends T
800
+ ? IfIndex
801
+ : T extends `${infer first}${infer rest}`
802
+ ? string extends first
803
+ ? IfIndex
804
+ : `${number}` extends first
805
+ ? IfIndex
806
+ : IfIndexKey<rest, IfIndex, IfLiteral>
807
+ : IfLiteral;
808
+
809
+ /**
810
+ * Helper for {@link JsonSerializableFilter} to determine if a property may
811
+ * be `undefined` and selects from options for result.
812
+ * Since `unknown` is a superset of `undefined`, it is given a special case.
813
+ * Additionally since index signatures are inherently optional, `unknown` typed
814
+ * values are treated as not undefined (`Result["Otherwise"]`).
815
+ *
816
+ * @system
817
+ */
818
+ export type IfPossiblyUndefinedProperty<
819
+ TKey,
820
+ TValue,
821
+ Result extends {
822
+ IfPossiblyUndefined: unknown;
823
+ IfUnknownNonIndexed: unknown;
824
+ Otherwise: unknown;
825
+ },
826
+ > = undefined extends TValue
827
+ ? unknown extends TValue
828
+ ? IfIndexKey<TKey, Result["Otherwise"], Result["IfUnknownNonIndexed"]>
829
+ : Result["IfPossiblyUndefined"]
830
+ : Result["Otherwise"];
831
+
832
+ /**
833
+ * Core implementation of {@link JsonSerializable}.
834
+ *
835
+ * @privateRemarks
836
+ * Filtering through a single layer of recursion is all that is required
837
+ * when using in prescribed filter scenario.
838
+ *
839
+ * @system
840
+ */
841
+ export type JsonSerializableFilter<
842
+ T,
843
+ Controls extends FilterControlsWithSubstitution,
844
+ TAncestorTypes extends unknown[],
845
+ TNextAncestor = T,
846
+ > = /* test for 'any' */ boolean extends (T extends never ? true : false)
847
+ ? /* 'any' => */ Controls["DegenerateSubstitute"]
848
+ : /* test for 'unknown' */ unknown extends T
849
+ ? /* 'unknown' => */ Controls["DegenerateSubstitute"]
850
+ : /* test for recursion */ IfExactTypeInTuple<
851
+ T,
852
+ TAncestorTypes,
853
+ true,
854
+ "no match"
855
+ > extends true
856
+ ? /* exact recursion; stop here => */ T
857
+ : /* test for JSON Encodable primitive types or given alternate base */ T extends
858
+ | null
859
+ | boolean
860
+ | number
861
+ | string
862
+ | Controls["AllowExtensionOf"]
863
+ ? /* primitive types or alternate => */ T
864
+ : /* test for exact alternate */ IfExactTypeInTuple<
865
+ T,
866
+ Controls["AllowExactly"],
867
+ true,
868
+ "no match"
869
+ > extends true
870
+ ? /* exact alternate type => */ T
871
+ : // eslint-disable-next-line @typescript-eslint/ban-types
872
+ /* test for not a function */ Extract<T, Function> extends never
873
+ ? /* not a function => test for object */ T extends object
874
+ ? /* object => test for array */ T extends readonly (infer _)[]
875
+ ? /* array => */ {
876
+ /* array items may not not allow undefined */
877
+ /* use homomorphic mapped type to preserve tuple type */
878
+ [K in keyof T]: JsonForSerializableArrayItem<
879
+ T[K],
880
+ Controls,
881
+ TAncestorTypes,
882
+ JsonSerializableFilter<
883
+ T[K],
884
+ Controls,
885
+ [TNextAncestor, ...TAncestorTypes]
886
+ >
887
+ >;
888
+ }
889
+ : /* not an array => test for exactly `object` */ IsExactlyObject<T> extends true
890
+ ? /* `object` => */ NonNullJsonObjectWith<
891
+ TupleToUnion<Controls["AllowExactly"]> | Controls["AllowExtensionOf"]
892
+ >
893
+ : /* test for enum like types */ IfEnumLike<T> extends never
894
+ ? /* enum or similar simple type (return as-is) => */ T
895
+ : /* property bag => */ FlattenIntersection<
896
+ {
897
+ /* required properties are recursed and may not have undefined values. */
898
+ [K in keyof T as RequiredNonSymbolKeysOf<
899
+ T,
900
+ K
901
+ >]-?: IfPossiblyUndefinedProperty<
902
+ K,
903
+ T[K],
904
+ {
905
+ IfPossiblyUndefined: {
906
+ ["error required property may not allow `undefined` value"]: never;
907
+ };
908
+ IfUnknownNonIndexed: {
909
+ ["error required property may not allow `unknown` value"]: never;
910
+ };
911
+ Otherwise: JsonSerializableFilter<
912
+ T[K],
913
+ Controls,
914
+ [TNextAncestor, ...TAncestorTypes]
915
+ >;
916
+ }
917
+ >;
918
+ } & {
919
+ /* optional properties are recursed and, when exactOptionalPropertyTypes is
920
+ false, are allowed to preserve undefined value type. */
921
+ [K in keyof T as OptionalNonSymbolKeysOf<
922
+ T,
923
+ K
924
+ >]?: JsonSerializableFilter<
925
+ T[K],
926
+ Controls,
927
+ [TNextAncestor, ...TAncestorTypes]
928
+ >;
929
+ } & {
930
+ /* symbol properties are rejected */
931
+ [K in keyof T & symbol]: never;
932
+ }
933
+ >
934
+ : /* not an object => */ never
935
+ : /* function => */ never;
936
+
937
+ // #endregion
938
+
939
+ // #region JsonDeserialized implementation
940
+
941
+ /**
942
+ * Sentinel type for use when marking points of recursion (in a recursive type).
943
+ * Type is expected to be unique, though no lengths are taken to ensure that.
944
+ *
945
+ * @system
946
+ */
947
+ export interface RecursionMarker {
948
+ [RecursionMarkerSymbol]: typeof RecursionMarkerSymbol;
949
+ }
950
+
951
+ /**
952
+ * Recursion limit is the count of `+` that prefix it when string.
953
+ *
954
+ * @system
955
+ */
956
+ export type RecursionLimit = `+${string}` | 0;
957
+
958
+ /**
959
+ * Outer implementation of {@link JsonDeserialized} handling meta cases
960
+ * like recursive types.
961
+ *
962
+ * @privateRemarks
963
+ * This utility is reentrant and will process a type `T` up to RecurseLimit.
964
+ *
965
+ * @system
966
+ */
967
+ export type JsonDeserializedImpl<
968
+ T,
969
+ Options extends Partial<FilterControls>,
970
+ RecurseLimit extends RecursionLimit = "++++" /* 4 */,
971
+ > = /* Build Controls from Options filling in defaults for any missing properties */
972
+ {
973
+ AllowExactly: Options extends { AllowExactly: unknown[] } ? Options["AllowExactly"] : [];
974
+ AllowExtensionOf: Options extends { AllowExtensionOf: unknown }
975
+ ? Options["AllowExtensionOf"]
976
+ : never;
977
+ // There Substitute types could be extracted to helper type, but are kept explicit here
978
+ // to make JsonTypeWith/NonNullJsonObjectWith show explicitly in results for users, rather
979
+ // than either the helper type name or a partially unrolled version.
980
+ DegenerateSubstitute: JsonTypeWith<
981
+ | (Options extends { AllowExactly: unknown[] }
982
+ ? TupleToUnion<Options["AllowExactly"]>
983
+ : never)
984
+ | (Options extends { AllowExtensionOf: unknown } ? Options["AllowExtensionOf"] : never)
985
+ >;
986
+ DegenerateNonNullObjectSubstitute: NonNullJsonObjectWith<
987
+ | (Options extends { AllowExactly: unknown[] }
988
+ ? TupleToUnion<Options["AllowExactly"]>
989
+ : never)
990
+ | (Options extends { AllowExtensionOf: unknown } ? Options["AllowExtensionOf"] : never)
991
+ >;
992
+ } extends infer Controls
993
+ ? /* Controls should always satisfy DeserializedFilterControls, but Typescript wants a check */
994
+ Controls extends DeserializedFilterControls
995
+ ? /* test for 'any' */ boolean extends (T extends never ? true : false)
996
+ ? /* 'any' => */ Controls["DegenerateSubstitute"]
997
+ : /* infer non-recursive version of T */ ReplaceRecursionWithMarkerAndPreserveAllowances<
998
+ T,
999
+ RecursionMarker,
1000
+ Controls
1001
+ > extends infer TNoRecursionAndOnlyPublics
1002
+ ? /* test for no change from filtered type */ IsSameType<
1003
+ TNoRecursionAndOnlyPublics,
1004
+ JsonDeserializedFilter<
1005
+ TNoRecursionAndOnlyPublics,
1006
+ {
1007
+ AllowExactly: [...Controls["AllowExactly"], RecursionMarker];
1008
+ AllowExtensionOf: Controls["AllowExtensionOf"];
1009
+ DegenerateSubstitute: Controls["DegenerateSubstitute"];
1010
+ DegenerateNonNullObjectSubstitute: Controls["DegenerateNonNullObjectSubstitute"];
1011
+ },
1012
+ 0
1013
+ >
1014
+ > extends true
1015
+ ? /* same (no filtering needed) => test for non-public
1016
+ properties (class instance type) */
1017
+ IfNonPublicProperties<
1018
+ T,
1019
+ // Note: no extra allowance is made here for possible branded
1020
+ // primitives as JsonDeserializedFilter will allow them as
1021
+ // extensions of the primitives. Should there need a need to
1022
+ // explicit allow them here, see JsonSerializableImpl's use.
1023
+ Controls,
1024
+ "found non-publics",
1025
+ "only publics"
1026
+ > extends "found non-publics"
1027
+ ? /* hidden props => apply filtering to avoid retaining
1028
+ exact class except for any classes in allowances => */
1029
+ JsonDeserializedFilter<
1030
+ T,
1031
+ Controls,
1032
+ // Note that use of RecurseLimit may not be needed here
1033
+ // could have an adverse effect on correctness if there
1034
+ // several ancestor types that require modification and
1035
+ // are peeling away the limit. In such a case, the limit
1036
+ // will be used for the problems and result is already
1037
+ // messy; so deferring full understanding of the problems
1038
+ // that could arise from a reset and being conservative.
1039
+ RecurseLimit
1040
+ >
1041
+ : /* no hidden properties => deserialized T is just T */
1042
+ T
1043
+ : /* filtering is needed => */ JsonDeserializedFilter<T, Controls, RecurseLimit>
1044
+ : /* unreachable else for infer */ never
1045
+ : never /* DeserializedFilterControls assert else; should never be reached */
1046
+ : never /* unreachable else for infer */;
1047
+
1048
+ /**
1049
+ * Recurses T applying {@link InternalUtilityTypes.JsonDeserializedFilter} up to RecurseLimit times.
1050
+ *
1051
+ * @system
1052
+ */
1053
+ export type JsonDeserializedRecursion<
1054
+ T,
1055
+ Controls extends DeserializedFilterControls,
1056
+ RecurseLimit extends RecursionLimit,
1057
+ TAncestorTypes,
1058
+ > = T extends TAncestorTypes
1059
+ ? RecurseLimit extends `+${infer RecursionRemainder}`
1060
+ ? /* Now that specific recursion is found, process that recursive type
1061
+ directly to avoid any collateral damage from ancestor type that
1062
+ required modification. */
1063
+ JsonDeserializedImpl<
1064
+ T,
1065
+ Controls,
1066
+ RecursionRemainder extends RecursionLimit ? RecursionRemainder : 0
1067
+ >
1068
+ : Controls["DegenerateSubstitute"]
1069
+ : JsonDeserializedFilter<T, Controls, RecurseLimit, TAncestorTypes | T>;
1070
+
1071
+ /**
1072
+ * Core implementation of {@link JsonDeserialized}.
1073
+ *
1074
+ * @system
1075
+ */
1076
+ export type JsonDeserializedFilter<
1077
+ T,
1078
+ Controls extends DeserializedFilterControls,
1079
+ RecurseLimit extends RecursionLimit,
1080
+ TAncestorTypes = T /* Always start with self as ancestor; otherwise recursion limit appears one greater */,
1081
+ > = /* test for 'any' */ boolean extends (T extends never ? true : false)
1082
+ ? /* 'any' => */ Controls["DegenerateSubstitute"]
1083
+ : /* test for 'unknown' */ unknown extends T
1084
+ ? /* 'unknown' => */ Controls["DegenerateSubstitute"]
1085
+ : /* test for deserializable primitive types or given alternate base */ T extends
1086
+ | null
1087
+ | boolean
1088
+ | number
1089
+ | string
1090
+ | Controls["AllowExtensionOf"]
1091
+ ? /* primitive types or alternate => */ T
1092
+ : /* test for given exact alternate */ IfExactTypeInTuple<
1093
+ T,
1094
+ Controls["AllowExactly"],
1095
+ true,
1096
+ "not found"
1097
+ > extends true
1098
+ ? /* exact alternate type => */ T
1099
+ : /* test for object */ T extends object
1100
+ ? /* object => */ ExtractFunctionFromIntersection<T> extends {
1101
+ classification: "exactly Function";
1102
+ }
1103
+ ? /* exactly function => */ never
1104
+ : /* not exactly a function (Function portion, if any, is omitted) */
1105
+ /* => test for array */ T extends readonly (infer _)[]
1106
+ ? /* array => */ {
1107
+ /* array items may not not allow undefined */
1108
+ /* use homomorphic mapped type to preserve tuple type */
1109
+ [K in keyof T]: JsonForDeserializedArrayItem<
1110
+ T[K],
1111
+ Controls,
1112
+ JsonDeserializedRecursion<T[K], Controls, RecurseLimit, TAncestorTypes>
1113
+ >;
1114
+ }
1115
+ : /* not an array => test for exactly `object` */ IsExactlyObject<T> extends true
1116
+ ? /* `object` => */ Controls["DegenerateNonNullObjectSubstitute"]
1117
+ : /* test for enum like types */ IfEnumLike<T> extends never
1118
+ ? /* enum or similar simple type (return as-is) => */ T
1119
+ : /* property bag => */ FlattenIntersection<
1120
+ /* properties with symbol keys or wholly unsupported values are removed */
1121
+ {
1122
+ /* properties with defined values are recursed */
1123
+ [K in keyof T as NonSymbolWithDeserializablePropertyOf<
1124
+ T,
1125
+ Controls["AllowExactly"],
1126
+ Controls["AllowExtensionOf"],
1127
+ K
1128
+ >]: JsonDeserializedRecursion<
1129
+ T[K],
1130
+ Controls,
1131
+ RecurseLimit,
1132
+ TAncestorTypes
1133
+ >;
1134
+ } & {
1135
+ /* properties that may have undefined values are optional */
1136
+ [K in keyof T as NonSymbolWithPossiblyDeserializablePropertyOf<
1137
+ T,
1138
+ Controls["AllowExactly"],
1139
+ Controls["AllowExtensionOf"],
1140
+ K
1141
+ >]?: JsonDeserializedRecursion<
1142
+ T[K],
1143
+ Controls,
1144
+ RecurseLimit,
1145
+ TAncestorTypes
1146
+ >;
1147
+ }
1148
+ >
1149
+ : /* not an object => */ never;
1150
+
1151
+ // #endregion
1152
+ }