@fluidframework/core-interfaces 2.51.0 → 2.52.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.
@@ -118,6 +118,11 @@ export namespace InternalUtilityTypes {
118
118
  * Typically this will be `NonNullJsonObjectWith<TupleToUnion<AllowExactly> | AllowExtensionOf>`.
119
119
  */
120
120
  DegenerateNonNullObjectSubstitute: unknown;
121
+
122
+ /**
123
+ * Either `RecursionMarker` when filtering after recursion has been replace or `never`
124
+ */
125
+ RecursionMarkerAllowed: unknown; // Can be RecursionMarker ?
121
126
  }
122
127
 
123
128
  /**
@@ -137,7 +142,12 @@ export namespace InternalUtilityTypes {
137
142
  [K in Keys]: T extends Record<K, T[K]> ? never : K;
138
143
  }[Keys],
139
144
  undefined | symbol
140
- >;
145
+ > extends infer Result
146
+ ? // Workaround for TypeScript bug/limitation where an alias for a type
147
+ // is not considered the same type when used as an index. This restores
148
+ // the original `Keys` (`keyof T`) type when there is no filtering.
149
+ IfSameType<Keys, Result, Keys, Extract<Result, string | number>>
150
+ : never;
141
151
 
142
152
  /**
143
153
  * Returns non-symbol keys for required properties of an object type.
@@ -156,7 +166,12 @@ export namespace InternalUtilityTypes {
156
166
  [K in Keys]: T extends Record<K, T[K]> ? K : never;
157
167
  }[Keys],
158
168
  undefined | symbol
159
- >;
169
+ > extends infer Result
170
+ ? // Workaround for TypeScript bug/limitation where an alias for a type
171
+ // is not considered the same type when used as an index. This restores
172
+ // the original `Keys` (`keyof T`) type when there is no filtering.
173
+ IfSameType<Keys, Result, Keys, Extract<Result, string | number>>
174
+ : never;
160
175
 
161
176
  /**
162
177
  * Returns Result.WhenSomethingDeserializable if T is sometimes at least a
@@ -259,9 +274,10 @@ export namespace InternalUtilityTypes {
259
274
 
260
275
  /**
261
276
  * Returns non-symbol keys for defined, (likely) serializable properties of an
262
- * object type. Keys with fully unsupported properties (undefined, symbol, and
263
- * bigint) and sometimes unsupported (functions) are excluded. An exception to
264
- * that is when there are supported types in union with just bigint.
277
+ * object type. Literal keys with fully unsupported properties (undefined, symbol,
278
+ * and bigint) and sometimes unsupported (functions) are excluded. An exception to
279
+ * that is when there are supported types in union with just bigint. Indexed keys
280
+ * are only excluded when there are no supported properties.
265
281
  *
266
282
  * For homomorphic mapping use with `as` to filter. Example:
267
283
  * `[K in keyof T as NonSymbolWithDeserializablePropertyOf<T, [], never, K>]: ...`
@@ -283,7 +299,11 @@ export namespace InternalUtilityTypes {
283
299
  ? IfSameType<
284
300
  PossibleTypeLessAllowed,
285
301
  unknown,
286
- /* value might not be supported => exclude K */ never,
302
+ /* value might not be supported => check for indexed key */ IfIndexOrBrandedKey<
303
+ K,
304
+ /* indexed => allow K */ K,
305
+ /* literal => exclude K */ never
306
+ >,
287
307
  /* extract types that might lead to missing property */ Extract<
288
308
  PossibleTypeLessAllowed,
289
309
  /* types that might lead to missing property, except `bigint` */
@@ -298,15 +318,32 @@ export namespace InternalUtilityTypes {
298
318
  /* exclusively supported types (and maybe `bigint`) or exactly `never` */
299
319
  /* => check for `never` */ T[K] extends never ? never : K
300
320
  >
301
- : /* value might not be supported => exclude K */ never
321
+ : /* value might not be supported => check for any supported */ TestDeserializabilityOf<
322
+ T[K],
323
+ OmitExactlyFromTuple<TExactExceptions, unknown>,
324
+ TExtendsException,
325
+ {
326
+ WhenSomethingDeserializable: /* => check for indexed key */ IfIndexOrBrandedKey<
327
+ K,
328
+ /* indexed => allow K */ K,
329
+ /* literal => exclude K */ never
330
+ >;
331
+ WhenNeverDeserializable: /* => exclude K */ never;
332
+ }
333
+ >
302
334
  >
303
335
  : never;
304
336
  }[Keys],
305
337
  undefined | symbol
306
- >;
338
+ > extends infer Result
339
+ ? // Workaround for TypeScript bug/limitation where an alias for a type
340
+ // is not considered the same type when used as an index. This restores
341
+ // the original `Keys` (`keyof T`) type when there is no filtering.
342
+ IfSameType<Keys, Result, Keys, Extract<Result, string | number>>
343
+ : never;
307
344
 
308
345
  /**
309
- * Returns non-symbol keys for partially supported properties of an object type.
346
+ * Returns non-symbol, literal keys for partially supported properties of an object type.
310
347
  * Keys with only unsupported properties (undefined, symbol, bigint, and
311
348
  * functions without other properties) are excluded.
312
349
  *
@@ -315,35 +352,48 @@ export namespace InternalUtilityTypes {
315
352
  *
316
353
  * @system
317
354
  */
318
- export type NonSymbolWithPossiblyDeserializablePropertyOf<
355
+ export type NonSymbolLiteralWithPossiblyDeserializablePropertyOf<
319
356
  T extends object,
320
357
  TExactExceptions extends unknown[],
321
358
  TExtendsException,
322
359
  Keys extends keyof T = keyof T,
323
360
  > = Exclude<
324
361
  {
325
- [K in Keys]: /* all possible types that aren't already allowed, with the exception of `unknown` */
326
- ExcludeExactlyInTuple<
327
- Exclude<T[K], TExtendsException>,
328
- OmitExactlyFromTuple<TExactExceptions, unknown>
329
- > extends infer PossibleTypeLessAllowed
330
- ? Extract<
331
- IfSameType<PossibleTypeLessAllowed, unknown, undefined, PossibleTypeLessAllowed>,
332
- /* types that might lead to missing property */
333
- // eslint-disable-next-line @typescript-eslint/ban-types
334
- undefined | symbol | Function
335
- > extends never
336
- ? /* exclusively supported types or exactly `never` */ never
337
- : /* at least some unsupported type => check for any supported */ TestDeserializabilityOf<
338
- T[K],
339
- OmitExactlyFromTuple<TExactExceptions, unknown>,
340
- TExtendsException,
341
- { WhenSomethingDeserializable: K; WhenNeverDeserializable: never }
342
- >
343
- : never;
362
+ [K in Keys]: IfIndexOrBrandedKey<
363
+ K,
364
+ /* indexed => exclude K */ never,
365
+ /* literal => ... */
366
+ /* all possible types that aren't already allowed, with the exception of `unknown` */
367
+ ExcludeExactlyInTuple<
368
+ Exclude<T[K], TExtendsException>,
369
+ OmitExactlyFromTuple<TExactExceptions, unknown>
370
+ > extends infer PossibleTypeLessAllowed
371
+ ? Extract<
372
+ IfSameType<PossibleTypeLessAllowed, unknown, undefined, PossibleTypeLessAllowed>,
373
+ /* types that might lead to missing property */
374
+ // eslint-disable-next-line @typescript-eslint/ban-types
375
+ undefined | symbol | Function
376
+ > extends never
377
+ ? /* exclusively supported types or exactly `never` */ never
378
+ : /* at least some unsupported type => check for any supported */ TestDeserializabilityOf<
379
+ T[K],
380
+ OmitExactlyFromTuple<TExactExceptions, unknown>,
381
+ TExtendsException,
382
+ {
383
+ WhenSomethingDeserializable: K;
384
+ WhenNeverDeserializable: never;
385
+ }
386
+ >
387
+ : never
388
+ >;
344
389
  }[Keys],
345
390
  undefined | symbol
346
- >;
391
+ > extends infer Result
392
+ ? // Workaround for TypeScript bug/limitation where an alias for a type
393
+ // is not considered the same type when used as an index. This restores
394
+ // the original `Keys` (`keyof T`) type when there is no filtering.
395
+ IfSameType<Keys, Result, Keys, Extract<Result, string | number>>
396
+ : never;
347
397
 
348
398
  /**
349
399
  * Filters a type `T` for `undefined` that is not viable in an array (or tuple) that
@@ -409,7 +459,7 @@ export namespace InternalUtilityTypes {
409
459
  ? /* primitive or replaced types => */ T
410
460
  : /* test for exact alternative */ IfExactTypeInTuple<
411
461
  T,
412
- Controls["AllowExactly"],
462
+ [...Controls["AllowExactly"], Controls["RecursionMarkerAllowed"]],
413
463
  /* exactly replaced => */ T,
414
464
  /* test for known types that become null */ T extends undefined | symbol
415
465
  ? /* => */ null
@@ -516,6 +566,47 @@ export namespace InternalUtilityTypes {
516
566
  /* T is NOT never => */ IfSameType<T, Extract<Union, T>, IfMatch, IfNoMatch>
517
567
  >;
518
568
 
569
+ /**
570
+ * Check for a template literal that has $\{string\} or $\{number\}
571
+ * in the pattern. Just `string` and/or `number` also match.
572
+ *
573
+ * @remarks This works recursively looking at first elements when not
574
+ * `string` or `number`. `first` will just be a single character if
575
+ * not $\{string\} or $\{number\}.
576
+ *
577
+ * @system
578
+ */
579
+ export type IfVariableStringOrNumber<T, IfVariable, IfLiteral> = `${string}` extends T
580
+ ? IfVariable
581
+ : number extends T
582
+ ? IfVariable
583
+ : T extends `${infer first}${infer rest}`
584
+ ? string extends first
585
+ ? IfVariable
586
+ : `${number}` extends first
587
+ ? IfVariable
588
+ : IfVariableStringOrNumber<rest, IfVariable, IfLiteral>
589
+ : IfLiteral;
590
+
591
+ /**
592
+ * Essentially a check for a non-fixed number or string OR a branded key.
593
+ *
594
+ * @remarks There is no known mechanism to determine the primitive from a
595
+ * generic tagged (branded) primitive, such as `X$\{string\}` & \{ foo: "bar" \}.
596
+ * There appears to be little use for a branded literal key -- at runtime
597
+ * there is no brand; so, two of the same literal with different brands would
598
+ * collide. Thus any branded key can usually be considered indexed.
599
+ *
600
+ * @system
601
+ */
602
+ export type IfIndexOrBrandedKey<
603
+ T extends keyof AnyRecord,
604
+ IfIndexOrBranded,
605
+ Otherwise,
606
+ > = T extends object
607
+ ? /* branded string or number */ IfIndexOrBranded
608
+ : /* => check for variable */ IfVariableStringOrNumber<T, IfIndexOrBranded, Otherwise>;
609
+
519
610
  /**
520
611
  * Test for type equality
521
612
  *
@@ -559,7 +650,7 @@ export namespace InternalUtilityTypes {
559
650
  : T;
560
651
 
561
652
  /**
562
- * Convenience constraint for any Opaque Json type.
653
+ * Convenience constraint for any OpaqueJson* type.
563
654
  *
564
655
  * @remarks
565
656
  * Use in extends check: `T extends AnyOpaqueJsonType`
@@ -825,9 +916,8 @@ export namespace InternalUtilityTypes {
825
916
  | boolean
826
917
  | number
827
918
  | string
828
- // Add in Opaque Json types
919
+ // Add in OpaqueJson* types
829
920
  | AnyOpaqueJsonType;
830
- DegenerateSubstitute: Controls["DegenerateSubstitute"];
831
921
  },
832
922
  "found non-publics",
833
923
  "only publics"
@@ -855,7 +945,7 @@ export namespace InternalUtilityTypes {
855
945
  : never /* unreachable else for infer */;
856
946
 
857
947
  /**
858
- * Handle Opaque Json types for {@link JsonSerializable}.
948
+ * Handle OpaqueJson* types for {@link JsonSerializable}.
859
949
  *
860
950
  * @remarks
861
951
  * {@link OpaqueJsonSerializable} and {@link OpaqueJsonDeserialized} instances
@@ -891,32 +981,10 @@ export namespace InternalUtilityTypes {
891
981
  Controls["AllowExactly"],
892
982
  Controls["AllowExtensionOf"]
893
983
  >
894
- : "internal error: failed to determine Opaque Json type"
984
+ : "internal error: failed to determine OpaqueJson* type"
895
985
  : never;
896
986
  /* eslint-enable @typescript-eslint/no-explicit-any */
897
987
 
898
- /**
899
- * Essentially a check for a template literal that has $\{string\} or
900
- * $\{number\} in the pattern. Just `string` and/or `number` also match.
901
- *
902
- * @remarks This works recursively looking at first elements when not
903
- * `string` or `number`. `first` will just be a single character if
904
- * not $\{string\} or $\{number\}.
905
- *
906
- * @system
907
- */
908
- export type IfIndexKey<T, IfIndex, IfLiteral> = `${string}` extends T
909
- ? IfIndex
910
- : number extends T
911
- ? IfIndex
912
- : T extends `${infer first}${infer rest}`
913
- ? string extends first
914
- ? IfIndex
915
- : `${number}` extends first
916
- ? IfIndex
917
- : IfIndexKey<rest, IfIndex, IfLiteral>
918
- : IfLiteral;
919
-
920
988
  /**
921
989
  * Helper for {@link JsonSerializableFilter} to determine if a property may
922
990
  * be `undefined` and selects from options for result.
@@ -927,7 +995,7 @@ export namespace InternalUtilityTypes {
927
995
  * @system
928
996
  */
929
997
  export type IfPossiblyUndefinedProperty<
930
- TKey,
998
+ TKey extends keyof AnyRecord,
931
999
  TValue,
932
1000
  Result extends {
933
1001
  IfPossiblyUndefined: unknown;
@@ -936,7 +1004,7 @@ export namespace InternalUtilityTypes {
936
1004
  },
937
1005
  > = undefined extends TValue
938
1006
  ? unknown extends TValue
939
- ? IfIndexKey<TKey, Result["Otherwise"], Result["IfUnknownNonIndexed"]>
1007
+ ? IfIndexOrBrandedKey<TKey, Result["Otherwise"], Result["IfUnknownNonIndexed"]>
940
1008
  : Result["IfPossiblyUndefined"]
941
1009
  : Result["Otherwise"];
942
1010
 
@@ -1003,8 +1071,8 @@ export namespace InternalUtilityTypes {
1003
1071
  >
1004
1072
  : /* test for enum like types */ IfEnumLike<T> extends never
1005
1073
  ? /* enum or similar simple type (return as-is) => */ T
1006
- : /* test for Opaque Json types */ T extends AnyOpaqueJsonType
1007
- ? /* Opaque Json type => */ JsonSerializableOpaqueAllowances<
1074
+ : /* test for OpaqueJson* types */ T extends AnyOpaqueJsonType
1075
+ ? /* OpaqueJson* type => */ JsonSerializableOpaqueAllowances<
1008
1076
  T,
1009
1077
  Controls
1010
1078
  >
@@ -1062,28 +1130,26 @@ export namespace InternalUtilityTypes {
1062
1130
  [RecursionMarkerSymbol]: typeof RecursionMarkerSymbol;
1063
1131
  }
1064
1132
 
1065
- /**
1066
- * Recursion limit is the count of `+` that prefix it when string.
1067
- *
1068
- * @system
1069
- */
1070
- export type RecursionLimit = `+${string}` | 0;
1071
-
1072
1133
  // #region JsonDeserialized implementation
1073
1134
 
1074
1135
  /**
1075
1136
  * Outer implementation of {@link JsonDeserialized} handling meta cases
1076
1137
  * like recursive types.
1077
1138
  *
1078
- * @privateRemarks
1079
- * This utility is reentrant and will process a type `T` up to `RecurseLimit`.
1139
+ * @remarks
1140
+ * If no modification is needed (`T` is exactly deserializable), `T` will
1141
+ * be the result.
1142
+ *
1143
+ * Upon recursion with `T` that requires modification, `T` is wrapped in
1144
+ * {@link OpaqueJsonDeserialized} to avoid further immediate processing.
1145
+ * Caller will need to unwrap the type to continue processing.
1080
1146
  *
1081
1147
  * @system
1082
1148
  */
1083
1149
  export type JsonDeserializedImpl<
1084
1150
  T,
1085
1151
  Options extends Partial<FilterControls>,
1086
- RecurseLimit extends RecursionLimit = "++++" /* 4 */,
1152
+ TypeUnderRecursion extends boolean = false,
1087
1153
  > = /* Build Controls from Options filling in defaults for any missing properties */
1088
1154
  {
1089
1155
  AllowExactly: Options extends { AllowExactly: unknown[] } ? Options["AllowExactly"] : [];
@@ -1105,6 +1171,7 @@ export namespace InternalUtilityTypes {
1105
1171
  : never)
1106
1172
  | (Options extends { AllowExtensionOf: unknown } ? Options["AllowExtensionOf"] : never)
1107
1173
  >;
1174
+ RecursionMarkerAllowed: never;
1108
1175
  } extends infer Controls
1109
1176
  ? /* Controls should always satisfy DeserializedFilterControls, but Typescript wants a check */
1110
1177
  Controls extends DeserializedFilterControls
@@ -1113,19 +1180,25 @@ export namespace InternalUtilityTypes {
1113
1180
  : /* infer non-recursive version of T */ ReplaceRecursionWithMarkerAndPreserveAllowances<
1114
1181
  T,
1115
1182
  RecursionMarker,
1116
- Controls
1183
+ {
1184
+ AllowExactly: Controls["AllowExactly"];
1185
+ AllowExtensionOf:
1186
+ | Controls["AllowExtensionOf"]
1187
+ // Also preserve OpaqueJson* types
1188
+ | AnyOpaqueJsonType;
1189
+ }
1117
1190
  > extends infer TNoRecursionAndOnlyPublics
1118
1191
  ? /* test for no change from filtered type */ IsSameType<
1119
1192
  TNoRecursionAndOnlyPublics,
1120
1193
  JsonDeserializedFilter<
1121
1194
  TNoRecursionAndOnlyPublics,
1122
1195
  {
1123
- AllowExactly: [...Controls["AllowExactly"], RecursionMarker];
1196
+ AllowExactly: Controls["AllowExactly"];
1124
1197
  AllowExtensionOf: Controls["AllowExtensionOf"];
1125
1198
  DegenerateSubstitute: Controls["DegenerateSubstitute"];
1126
1199
  DegenerateNonNullObjectSubstitute: Controls["DegenerateNonNullObjectSubstitute"];
1127
- },
1128
- 0
1200
+ RecursionMarkerAllowed: RecursionMarker;
1201
+ }
1129
1202
  >
1130
1203
  > extends true
1131
1204
  ? /* same (no filtering needed) => test for non-public
@@ -1134,58 +1207,85 @@ export namespace InternalUtilityTypes {
1134
1207
  T,
1135
1208
  // Note: no extra allowance is made here for possible branded
1136
1209
  // primitives as JsonDeserializedFilter will allow them as
1137
- // extensions of the primitives. Should there need a need to
1138
- // explicit allow them here, see JsonSerializableImpl's use.
1139
- Controls,
1210
+ // extensions of the primitives. Should there be a need to
1211
+ // explicitly allow them here, see JsonSerializableImpl's use.
1212
+ {
1213
+ AllowExactly: Controls["AllowExactly"];
1214
+ AllowExtensionOf:
1215
+ | Controls["AllowExtensionOf"]
1216
+ // Add in OpaqueJson* types
1217
+ | AnyOpaqueJsonType;
1218
+ },
1140
1219
  "found non-publics",
1141
1220
  "only publics"
1142
1221
  > extends "found non-publics"
1143
1222
  ? /* hidden props => apply filtering to avoid retaining
1144
- exact class except for any classes in allowances => */
1145
- JsonDeserializedFilter<
1146
- T,
1147
- Controls,
1148
- // Note that use of RecurseLimit may not be needed here
1149
- // could have an adverse effect on correctness if there
1150
- // several ancestor types that require modification and
1151
- // are peeling away the limit. In such a case, the limit
1152
- // will be used for the problems and result is already
1153
- // messy; so deferring full understanding of the problems
1154
- // that could arise from a reset and being conservative.
1155
- RecurseLimit
1156
- >
1223
+ exact class except for any classes in allowances =>
1224
+ test for known recursion */
1225
+ TypeUnderRecursion extends false
1226
+ ? /* no known recursion => */ JsonDeserializedFilter<T, Controls>
1227
+ : /* known recursion => use OpaqueJsonDeserialized for later processing */
1228
+ OpaqueJsonDeserialized<
1229
+ T,
1230
+ Controls["AllowExactly"],
1231
+ Controls["AllowExtensionOf"]
1232
+ >
1157
1233
  : /* no hidden properties => deserialized T is just T */
1158
1234
  T
1159
- : /* filtering is needed => */ JsonDeserializedFilter<T, Controls, RecurseLimit>
1235
+ : /* filtering is needed => test for known recursion */ TypeUnderRecursion extends false
1236
+ ? /* no known recursion => */ JsonDeserializedFilter<T, Controls>
1237
+ : /* known recursion => use OpaqueJsonDeserialized for later processing */
1238
+ OpaqueJsonDeserialized<
1239
+ T,
1240
+ Controls["AllowExactly"],
1241
+ Controls["AllowExtensionOf"]
1242
+ >
1160
1243
  : /* unreachable else for infer */ never
1161
1244
  : never /* DeserializedFilterControls assert else; should never be reached */
1162
1245
  : never /* unreachable else for infer */;
1163
1246
 
1164
1247
  /**
1165
- * Recurses `T` applying {@link InternalUtilityTypes.JsonDeserializedFilter} up to `RecurseLimit` times.
1248
+ * Recurses `T` applying {@link InternalUtilityTypes.JsonDeserializedFilter} up until
1249
+ * `T` is found to be a match of an ancestor type. At that point `T` is wrapped in
1250
+ * {@link OpaqueJsonDeserialized} to avoid further immediate processing, if
1251
+ * modification is needed. Caller will need to unwrap the type to continue processing.
1252
+ * If no modification is needed, then `T` will be the result.
1253
+ *
1254
+ * @privateRemarks Exact recursion pattern employed should allow shortcut
1255
+ * test to return `OpaqueJsonDeserialized<T>` from here when the exact
1256
+ * recursion match is the first ancestor type in tuple as it must have been
1257
+ * processed and found to need modification.
1166
1258
  *
1167
1259
  * @system
1168
1260
  */
1169
1261
  export type JsonDeserializedRecursion<
1170
1262
  T,
1171
1263
  Controls extends DeserializedFilterControls,
1172
- RecurseLimit extends RecursionLimit,
1173
- TAncestorTypes,
1174
- > = T extends TAncestorTypes
1175
- ? RecurseLimit extends `+${infer RecursionRemainder}`
1176
- ? /* Now that specific recursion is found, process that recursive type
1177
- directly to avoid any collateral damage from ancestor type that
1178
- required modification. */
1179
- JsonDeserializedImpl<
1264
+ TAncestorTypes extends object[],
1265
+ > = IfExactTypeInTuple<T, TAncestorTypes, true, "no match"> extends true
1266
+ ? /* recursion found => reprocess that recursive type directly to avoid
1267
+ any collateral damage from ancestor type that required modification.
1268
+ Further recursion will not happen during processing. Either:
1269
+ - the type requires modification and the `TypeUnderRecursion=true`
1270
+ arg will result in `OpaqueJsonDeserialized<T>` wrapper OR
1271
+ - no change is needed from this point and `T` will result. */
1272
+ JsonDeserializedImpl<T, Controls, true>
1273
+ : T extends object
1274
+ ? IfExactTypeInTuple<T, TAncestorTypes, true, "no match"> extends true
1275
+ ? JsonDeserializedImpl<T, Controls, true>
1276
+ : /* no recursion yet detected => */ JsonDeserializedFilter<
1277
+ T,
1278
+ Controls,
1279
+ [...TAncestorTypes, T]
1280
+ >
1281
+ : /* not an object (no recursion) => */ JsonDeserializedFilter<
1180
1282
  T,
1181
1283
  Controls,
1182
- RecursionRemainder extends RecursionLimit ? RecursionRemainder : 0
1183
- >
1184
- : Controls["DegenerateSubstitute"]
1185
- : JsonDeserializedFilter<T, Controls, RecurseLimit, TAncestorTypes | T>;
1284
+ TAncestorTypes
1285
+ >;
1186
1286
 
1187
1287
  /**
1188
- * Handle Opaque Json types for {@link JsonDeserialized}.
1288
+ * Handle OpaqueJson* types for {@link JsonDeserialized}.
1189
1289
  *
1190
1290
  * @remarks
1191
1291
  * {@link OpaqueJsonSerializable} instances are converted to {@link OpaqueJsonDeserialized}.
@@ -1210,7 +1310,7 @@ export namespace InternalUtilityTypes {
1210
1310
  | OpaqueJsonSerializable<infer TData, any, unknown>
1211
1311
  | OpaqueJsonDeserialized<infer TData, any, unknown>
1212
1312
  ? OpaqueJsonDeserialized<TData, Controls["AllowExactly"], Controls["AllowExtensionOf"]>
1213
- : "internal error: failed to determine Opaque Json type";
1313
+ : "internal error: failed to determine OpaqueJson* type";
1214
1314
  /* eslint-enable @typescript-eslint/no-explicit-any */
1215
1315
 
1216
1316
  /**
@@ -1221,8 +1321,9 @@ export namespace InternalUtilityTypes {
1221
1321
  export type JsonDeserializedFilter<
1222
1322
  T,
1223
1323
  Controls extends DeserializedFilterControls,
1224
- RecurseLimit extends RecursionLimit,
1225
- TAncestorTypes = T /* Always start with self as ancestor; otherwise recursion limit appears one greater */,
1324
+ // Always start with object portion of self as ancestor. Filtering will
1325
+ // not apply past recursion (nested occurrence of this).
1326
+ TAncestorTypes extends object[] = [Extract<T, object>],
1226
1327
  > = /* test for 'any' */ boolean extends (T extends never ? true : false)
1227
1328
  ? /* 'any' => */ Controls["DegenerateSubstitute"]
1228
1329
  : /* test for 'unknown' */ unknown extends T
@@ -1236,7 +1337,7 @@ export namespace InternalUtilityTypes {
1236
1337
  ? /* primitive types or alternate => */ T
1237
1338
  : /* test for given exact alternate */ IfExactTypeInTuple<
1238
1339
  T,
1239
- Controls["AllowExactly"],
1340
+ [...Controls["AllowExactly"], Controls["RecursionMarkerAllowed"]],
1240
1341
  true,
1241
1342
  "not found"
1242
1343
  > extends true
@@ -1254,43 +1355,39 @@ export namespace InternalUtilityTypes {
1254
1355
  [K in keyof T]: JsonForDeserializedArrayItem<
1255
1356
  T[K],
1256
1357
  Controls,
1257
- JsonDeserializedRecursion<T[K], Controls, RecurseLimit, TAncestorTypes>
1358
+ JsonDeserializedRecursion<T[K], Controls, TAncestorTypes>
1258
1359
  >;
1259
1360
  }
1260
1361
  : /* not an array => test for exactly `object` */ IsExactlyObject<T> extends true
1261
1362
  ? /* `object` => */ Controls["DegenerateNonNullObjectSubstitute"]
1262
1363
  : /* test for enum like types */ IfEnumLike<T> extends never
1263
1364
  ? /* enum or similar simple type (return as-is) => */ T
1264
- : /* test for matching Opaque Json types */ T extends AnyOpaqueJsonType
1265
- ? /* Opaque Json type => */ JsonDeserializedOpaqueConversion<T, Controls>
1365
+ : /* test for matching OpaqueJson* types */ T extends AnyOpaqueJsonType
1366
+ ? /* OpaqueJson* type => */ JsonDeserializedOpaqueConversion<T, Controls>
1266
1367
  : /* property bag => */ FlattenIntersection<
1267
1368
  /* properties with symbol keys or wholly unsupported values are removed */
1268
1369
  {
1269
1370
  /* properties with defined values are recursed */
1270
1371
  [K in keyof T as NonSymbolWithDeserializablePropertyOf<
1271
1372
  T,
1272
- Controls["AllowExactly"],
1373
+ [
1374
+ ...Controls["AllowExactly"],
1375
+ Controls["RecursionMarkerAllowed"],
1376
+ ],
1273
1377
  Controls["AllowExtensionOf"],
1274
1378
  K
1275
- >]: JsonDeserializedRecursion<
1276
- T[K],
1277
- Controls,
1278
- RecurseLimit,
1279
- TAncestorTypes
1280
- >;
1379
+ >]: JsonDeserializedRecursion<T[K], Controls, TAncestorTypes>;
1281
1380
  } & {
1282
- /* properties that may have undefined values are optional */
1283
- [K in keyof T as NonSymbolWithPossiblyDeserializablePropertyOf<
1381
+ /* literal properties that may have undefined values are optional */
1382
+ [K in keyof T as NonSymbolLiteralWithPossiblyDeserializablePropertyOf<
1284
1383
  T,
1285
- Controls["AllowExactly"],
1384
+ [
1385
+ ...Controls["AllowExactly"],
1386
+ Controls["RecursionMarkerAllowed"],
1387
+ ],
1286
1388
  Controls["AllowExtensionOf"],
1287
1389
  K
1288
- >]?: JsonDeserializedRecursion<
1289
- T[K],
1290
- Controls,
1291
- RecurseLimit,
1292
- TAncestorTypes
1293
- >;
1390
+ >]?: JsonDeserializedRecursion<T[K], Controls, TAncestorTypes>;
1294
1391
  }
1295
1392
  >
1296
1393
  : /* not an object => */ never;
@@ -1299,6 +1396,13 @@ export namespace InternalUtilityTypes {
1299
1396
 
1300
1397
  // #region *Readonly implementations
1301
1398
 
1399
+ /**
1400
+ * Recursion limit is the count of `+` that prefix it when string.
1401
+ *
1402
+ * @system
1403
+ */
1404
+ export type RecursionLimit = `+${string}` | 0;
1405
+
1302
1406
  /**
1303
1407
  * If `T` is a `Map<K,V>` or `ReadonlyMap<K,V>`, returns `ReadonlyMap` and,
1304
1408
  * if `T` extends `DeepenedGenerics`, {@link DeepReadonly} is applied to