ts-data-forge 14.10.0 → 14.10.1

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 (88) hide show
  1. package/README.md +5 -5
  2. package/dist/array/impl/array-utils-element-access.d.mts.map +1 -1
  3. package/dist/array/impl/array-utils-element-access.mjs +4 -4
  4. package/dist/array/impl/array-utils-element-access.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +8 -8
  6. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  7. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +28 -28
  8. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
  10. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +4 -4
  11. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
  12. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +3 -4
  13. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -1
  14. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +28 -29
  15. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -1
  16. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -1
  17. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +3 -3
  18. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -1
  19. package/dist/array/impl/array-utils-modification.d.mts +1 -1
  20. package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
  21. package/dist/array/impl/array-utils-modification.mjs +15 -15
  22. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  23. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  24. package/dist/array/impl/array-utils-reducing-value.mjs +12 -12
  25. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  26. package/dist/array/impl/array-utils-search.d.mts.map +1 -1
  27. package/dist/array/impl/array-utils-search.mjs +56 -56
  28. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  29. package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
  30. package/dist/array/impl/array-utils-slicing.mjs +3 -3
  31. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  32. package/dist/array/impl/array-utils-transformation.d.mts +25 -25
  33. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  34. package/dist/array/impl/array-utils-transformation.mjs +22 -22
  35. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  36. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  37. package/dist/array/impl/array-utils-validation.mjs +2 -2
  38. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  39. package/dist/collections/imap-mapped.d.mts +2 -2
  40. package/dist/collections/imap.d.mts +3 -4
  41. package/dist/collections/imap.d.mts.map +1 -1
  42. package/dist/collections/imap.mjs +0 -1
  43. package/dist/collections/imap.mjs.map +1 -1
  44. package/dist/collections/iset-mapped.d.mts +1 -2
  45. package/dist/collections/iset-mapped.d.mts.map +1 -1
  46. package/dist/collections/iset-mapped.mjs.map +1 -1
  47. package/dist/collections/iset.d.mts +4 -5
  48. package/dist/collections/iset.d.mts.map +1 -1
  49. package/dist/collections/iset.mjs +0 -1
  50. package/dist/collections/iset.mjs.map +1 -1
  51. package/dist/number/num.d.mts +2 -2
  52. package/dist/number/num.mjs.map +1 -1
  53. package/dist/object/object.d.mts +2 -2
  54. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  55. package/dist/others/fast-deep-equal.mjs +10 -7
  56. package/dist/others/fast-deep-equal.mjs.map +1 -1
  57. package/dist/others/replace-equal-deep.d.mts +9 -9
  58. package/dist/others/replace-equal-deep.mjs +12 -12
  59. package/dist/others/replace-equal-deep.mjs.map +1 -1
  60. package/dist/string/str.d.mts +2 -2
  61. package/package.json +12 -12
  62. package/src/array/impl/array-utils-element-access.mts +11 -11
  63. package/src/array/impl/array-utils-length-bounded-array-cast.mts +69 -69
  64. package/src/array/impl/array-utils-length-bounded-array-guard.mts +21 -21
  65. package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +2 -2
  66. package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +64 -65
  67. package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +15 -15
  68. package/src/array/impl/array-utils-modification.mts +54 -54
  69. package/src/array/impl/array-utils-reducing-value.mts +55 -55
  70. package/src/array/impl/array-utils-search.mts +114 -114
  71. package/src/array/impl/array-utils-slicing.mts +16 -16
  72. package/src/array/impl/array-utils-transformation.mts +96 -96
  73. package/src/array/impl/array-utils-transformation.test.mts +2 -2
  74. package/src/array/impl/array-utils-validation.mts +19 -19
  75. package/src/array/impl/array-utils-validation.test.mts +1 -1
  76. package/src/collections/imap-mapped.mts +2 -2
  77. package/src/collections/imap.mts +3 -4
  78. package/src/collections/imap.test.mts +1 -1
  79. package/src/collections/iset-mapped.mts +1 -2
  80. package/src/collections/iset.mts +4 -5
  81. package/src/collections/iset.test.mts +1 -1
  82. package/src/number/num.mts +10 -10
  83. package/src/object/object.mts +2 -2
  84. package/src/others/fast-deep-equal.mts +17 -10
  85. package/src/others/fast-deep-equal.test.mts +141 -29
  86. package/src/others/replace-equal-deep.mts +37 -37
  87. package/src/others/replace-equal-deep.test.mts +13 -7
  88. package/src/string/str.mts +2 -2
@@ -203,6 +203,18 @@ describe(fastDeepEqual, () => {
203
203
  y: { toString: () => 'Hello world!' } as any,
204
204
  expected: '=',
205
205
  },
206
+ {
207
+ name: 'objects whose own `valueOf` and `toString` are not functions are compared by key',
208
+ x: { valueOf: 1, toString: 'a' } as any,
209
+ y: { valueOf: 1, toString: 'a' } as any,
210
+ expected: '=',
211
+ },
212
+ {
213
+ name: 'objects whose own `valueOf` that is not a function differs are not equal',
214
+ x: { valueOf: 1 } as any,
215
+ y: { valueOf: 2 } as any,
216
+ expected: '!=',
217
+ },
206
218
  {
207
219
  name: 'objects with `toString` functions returning different values are not equal',
208
220
  x: { toString: () => 'Hello world!' } as any,
@@ -212,6 +224,54 @@ describe(fastDeepEqual, () => {
212
224
  ] as const satisfies readonly TestCase[])('$name', testFn);
213
225
  });
214
226
 
227
+ describe('null-prototype objects', () => {
228
+ const withNullPrototype = (
229
+ entries: ReadonlyRecord<string, unknown>,
230
+ ): unknown => Object.setPrototypeOf({ ...entries }, null);
231
+
232
+ const groupByParity = (xs: readonly number[]): unknown =>
233
+ Object.groupBy(xs, (x) => (x % 2 === 0 ? 'even' : 'odd'));
234
+
235
+ test.each([
236
+ {
237
+ name: 'empty null-prototype objects are equal',
238
+ x: Object.create(null),
239
+ y: Object.create(null),
240
+ expected: '=',
241
+ },
242
+ {
243
+ name: 'null-prototype objects with the same entries are equal',
244
+ x: withNullPrototype({ a: 1, b: [2] }),
245
+ y: withNullPrototype({ a: 1, b: [2] }),
246
+ expected: '=',
247
+ },
248
+ {
249
+ name: 'null-prototype objects with different entries are not equal',
250
+ x: withNullPrototype({ a: 1 }),
251
+ y: withNullPrototype({ a: 2 }),
252
+ expected: '!=',
253
+ },
254
+ {
255
+ name: 'a null-prototype object and a plain object are not equal',
256
+ x: Object.create(null),
257
+ y: {},
258
+ expected: '!=',
259
+ },
260
+ {
261
+ name: 'Object.groupBy results with the same groups are equal',
262
+ x: groupByParity([1, 2, 3]),
263
+ y: groupByParity([1, 2, 3]),
264
+ expected: '=',
265
+ },
266
+ {
267
+ name: 'Object.groupBy results with different groups are not equal',
268
+ x: groupByParity([1, 2, 3]),
269
+ y: groupByParity([1, 2, 4]),
270
+ expected: '!=',
271
+ },
272
+ ] as const satisfies readonly TestCase[])('$name', testFn);
273
+ });
274
+
215
275
  describe('arrays', () => {
216
276
  test.each([
217
277
  { name: 'two empty arrays are equal', x: [], y: [], expected: '=' },
@@ -382,13 +442,13 @@ describe(fastDeepEqual, () => {
382
442
  test.each([
383
443
  {
384
444
  name: 'empty maps are equal',
385
- x: new Map(),
386
- y: new Map(),
445
+ x: new Map<unknown, unknown>(),
446
+ y: new Map<unknown, unknown>(),
387
447
  expected: '=',
388
448
  },
389
449
  {
390
450
  name: 'empty maps of different class are not equal',
391
- x: new Map(),
451
+ x: new Map<unknown, unknown>(),
392
452
  y: new MyMap(),
393
453
  expected: '!=',
394
454
  },
@@ -436,32 +496,44 @@ describe(fastDeepEqual, () => {
436
496
  },
437
497
  {
438
498
  name: 'equal maps (same sub-keys)',
439
- x: new Map([['a', [createMap({ b: 'c' })]]]),
440
- y: new Map([['a', [createMap({ b: 'c' })]]]),
499
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
500
+ ['a', [createMap({ b: 'c' })]],
501
+ ]),
502
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
503
+ ['a', [createMap({ b: 'c' })]],
504
+ ]),
441
505
  expected: '=',
442
506
  },
443
507
  {
444
508
  name: 'not equal maps (different sub-key value)',
445
- x: new Map([['a', [createMap({ b: 'c' })]]]),
446
- y: new Map([['a', [createMap({ b: 'd' })]]]),
509
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
510
+ ['a', [createMap({ b: 'c' })]],
511
+ ]),
512
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
513
+ ['a', [createMap({ b: 'd' })]],
514
+ ]),
447
515
  expected: '!=',
448
516
  },
449
517
  {
450
518
  name: 'not equal maps (different sub-key)',
451
- x: new Map([['a', [createMap({ b: 'c' })]]]),
452
- y: new Map([['a', [createMap({ c: 'c' })]]]),
519
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
520
+ ['a', [createMap({ b: 'c' })]],
521
+ ]),
522
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
523
+ ['a', [createMap({ c: 'c' })]],
524
+ ]),
453
525
  expected: '!=',
454
526
  },
455
527
  {
456
528
  name: 'empty map and empty object are equal',
457
529
  x: {},
458
- y: new Map(),
530
+ y: new Map<unknown, unknown>(),
459
531
  expected: '!=',
460
532
  },
461
533
  {
462
534
  name: 'empty map and empty array are not equal',
463
535
  x: [],
464
- y: new Map(),
536
+ y: new Map<unknown, unknown>(),
465
537
  expected: '!=',
466
538
  },
467
539
  {
@@ -514,13 +586,13 @@ describe(fastDeepEqual, () => {
514
586
  test.each([
515
587
  {
516
588
  name: 'empty sets are equal',
517
- x: new Set(),
518
- y: new Set(),
589
+ x: new Set<unknown>(),
590
+ y: new Set<unknown>(),
519
591
  expected: '=',
520
592
  },
521
593
  {
522
594
  name: 'empty sets of different class are not equal',
523
- x: new Set(),
595
+ x: new Set<unknown>(),
524
596
  y: new MySet(),
525
597
  expected: '!=',
526
598
  },
@@ -575,13 +647,13 @@ describe(fastDeepEqual, () => {
575
647
  {
576
648
  name: 'empty set and empty object are not equal',
577
649
  x: {},
578
- y: new Set(),
650
+ y: new Set<unknown>(),
579
651
  expected: '!=',
580
652
  },
581
653
  {
582
654
  name: 'empty set and empty array are not equal',
583
655
  x: [],
584
- y: new Set(),
656
+ y: new Set<unknown>(),
585
657
  expected: '!=',
586
658
  },
587
659
  {
@@ -598,7 +670,7 @@ describe(fastDeepEqual, () => {
598
670
  },
599
671
  {
600
672
  name: 'set and pseudo set are not equal',
601
- x: new Set(),
673
+ x: new Set<unknown>(),
602
674
  y: {
603
675
  constructor: Set,
604
676
  size: 0,
@@ -702,15 +774,58 @@ describe(fastDeepEqual, () => {
702
774
  'content',
703
775
  );
704
776
 
705
- // These elements have the same structure but different _owner references
706
- // fastDeepEqual should skip _owner comparison and not cause infinite loop
707
- // The result depends on whether other properties are equal
708
- const result = fastDeepEqualUntyped(element1, element2);
709
-
710
- // The important thing is that it doesn't throw or hang
711
- assert.isBoolean(result);
777
+ assert.isTrue(fastDeepEqualUntyped(element1, element2));
712
778
  });
713
779
 
780
+ test.each([
781
+ {
782
+ name: 'element-shaped objects with different _owner are equal',
783
+ x: {
784
+ $$typeof: Symbol.for('react.element'),
785
+ type: 'div',
786
+ key: null,
787
+ props: { className: 'x' },
788
+ _owner: { name: 'A' },
789
+ },
790
+ y: {
791
+ $$typeof: Symbol.for('react.element'),
792
+ type: 'div',
793
+ key: null,
794
+ props: { className: 'x' },
795
+ _owner: { name: 'B' },
796
+ },
797
+ expected: '=',
798
+ },
799
+ {
800
+ name: 'element-shaped objects with different props are not equal',
801
+ x: {
802
+ $$typeof: Symbol.for('react.element'),
803
+ type: 'div',
804
+ props: { className: 'x' },
805
+ _owner: null,
806
+ },
807
+ y: {
808
+ $$typeof: Symbol.for('react.element'),
809
+ type: 'div',
810
+ props: { className: 'y' },
811
+ _owner: null,
812
+ },
813
+ expected: '!=',
814
+ },
815
+ {
816
+ name: '_owner is compared on an object without $$typeof',
817
+ x: { _owner: { name: 'A' } },
818
+ y: { _owner: { name: 'B' } },
819
+ expected: '!=',
820
+ },
821
+ {
822
+ name: '_owner is compared when $$typeof is falsy',
823
+ x: { $$typeof: null, _owner: { name: 'A' } },
824
+ y: { $$typeof: null, _owner: { name: 'B' } },
825
+ expected: '!=',
826
+ },
827
+ ] as const satisfies readonly TestCase[])('$name', testFn);
828
+
714
829
  test('React elements with different props are not equal', () => {
715
830
  const element1 = React.createElement('div', { className: 'test1' });
716
831
 
@@ -747,9 +862,7 @@ describe(fastDeepEqual, () => {
747
862
  child.render();
748
863
  }
749
864
 
750
- // Should still be 1 or 2 depending on React element comparison
751
- // The important thing is it doesn't throw or hang
752
- assert.isTrue(mut_renderCount >= 1);
865
+ assert.strictEqual(mut_renderCount, 1);
753
866
 
754
867
  // Update with different props - should re-render
755
868
  const differentProps = {
@@ -763,8 +876,7 @@ describe(fastDeepEqual, () => {
763
876
  child.render();
764
877
  }
765
878
 
766
- // Should have rendered at least once more
767
- assert.isTrue(mut_renderCount >= 2);
879
+ assert.strictEqual(mut_renderCount, 2);
768
880
  });
769
881
  });
770
882
  });
@@ -7,18 +7,18 @@ import { fastDeepEqual } from './fast-deep-equal.mjs';
7
7
  * replaced by that part of `current` (structural sharing). When the whole of
8
8
  * `next` is deeply equal to `current`, `current` itself is returned, so an
9
9
  * `Object.is` comparison of the result with `current` tells whether anything
10
- * changed, and a reader that compares by reference sees a new object only
11
- * where the value really changed.
10
+ * changed, and a reader that compares by reference sees a new object only where
11
+ * the value really changed.
12
12
  *
13
13
  * Only plain objects and arrays are walked into. Anything else — a `Map`, a
14
14
  * `Set`, a `Date`, a class instance — is compared whole with
15
15
  * {@link fastDeepEqual} and kept or replaced whole.
16
16
  *
17
17
  * Array items are matched by index. Give `key` to match them by key instead:
18
- * an item for which it returns something other than `undefined` is matched
19
- * with the item of `current` it returns the same key for, and an item it
20
- * returns `undefined` for is still matched by index. The one `key` is used
21
- * for the arrays at every depth.
18
+ * an item for which it returns something other than `undefined` is matched with
19
+ * the item of `current` it returns the same key for, and an item it returns
20
+ * `undefined` for is still matched by index. The one `key` is used for the
21
+ * arrays at every depth.
22
22
  *
23
23
  * Neither argument is changed. The result shares objects with both, so it
24
24
  * relies on neither being changed afterwards.
@@ -91,23 +91,29 @@ export const replaceEqualDeep = <T,>(
91
91
  next: T,
92
92
  options?: ReplaceEqualDeepOptions,
93
93
  ): T =>
94
- // The walk sees `unknown`; what it returns is `current`, `next`, or a copy
95
- // of `next`'s shape whose parts are parts of either, so it is a `T`.
94
+ // The walk sees `unknown`; what it returns is `current`, `next`, or a copy of
95
+ // `next`'s shape whose parts are parts of either, so it is a `T`.
96
96
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
97
97
  replace(current, next, options?.key) as T;
98
98
 
99
99
  /** Options of {@link replaceEqualDeep}. */
100
100
  export type ReplaceEqualDeepOptions = Readonly<{
101
101
  /**
102
- * The key an array item is matched by. Items it returns `undefined` for
103
- * are matched by index. Keys are compared with `SameValueZero`, as `Map`
104
- * compares them.
102
+ * The key an array item is matched by. Items it returns `undefined` for are
103
+ * matched by index. Keys are compared with `SameValueZero`, as `Map` compares
104
+ * them.
105
105
  */
106
106
  key?: (item: unknown) => unknown;
107
107
  }>;
108
108
 
109
109
  type Key = (item: unknown) => unknown;
110
110
 
111
+ const isPlainArray = (value: unknown): value is readonly unknown[] =>
112
+ Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
113
+
114
+ const isPlainObject = (value: unknown): value is UnknownRecord =>
115
+ isRecord(value) && Object.getPrototypeOf(value) === Object.prototype;
116
+
111
117
  const replace = (
112
118
  current: unknown,
113
119
  next: unknown,
@@ -123,9 +129,26 @@ const replace = (
123
129
  ? current
124
130
  : next;
125
131
 
126
- // Each walk returns `current` when every part of the result is the same part
127
- // of `current`, `next` when every part is the same part of `next` (nothing
128
- // was reused, so no copy is needed), and the copy otherwise.
132
+ const indexByKey = (
133
+ items: readonly unknown[],
134
+ key: Key,
135
+ ): ReadonlyMap<unknown, unknown> => {
136
+ const mut_byKey = new Map<unknown, unknown>();
137
+
138
+ for (const item of items) {
139
+ const itemKey = key(item);
140
+
141
+ if (itemKey !== undefined && !mut_byKey.has(itemKey)) {
142
+ mut_byKey.set(itemKey, item);
143
+ }
144
+ }
145
+
146
+ return mut_byKey;
147
+ };
148
+
149
+ // Each walk returns `current` when every part of the result is the same part of
150
+ // `current`, `next` when every part is the same part of `next` (nothing was
151
+ // reused, so no copy is needed), and the copy otherwise.
129
152
 
130
153
  const replaceArray = (
131
154
  current: readonly unknown[],
@@ -200,26 +223,3 @@ const replaceObject = (
200
223
 
201
224
  // What an item of `next` is matched with when nothing of `current` is.
202
225
  const absent: unique symbol = Symbol('absent');
203
-
204
- const indexByKey = (
205
- items: readonly unknown[],
206
- key: Key,
207
- ): ReadonlyMap<unknown, unknown> => {
208
- const mut_byKey = new Map<unknown, unknown>();
209
-
210
- for (const item of items) {
211
- const itemKey = key(item);
212
-
213
- if (itemKey !== undefined && !mut_byKey.has(itemKey)) {
214
- mut_byKey.set(itemKey, item);
215
- }
216
- }
217
-
218
- return mut_byKey;
219
- };
220
-
221
- const isPlainArray = (value: unknown): value is readonly unknown[] =>
222
- Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
223
-
224
- const isPlainObject = (value: unknown): value is UnknownRecord =>
225
- isRecord(value) && Object.getPrototypeOf(value) === Object.prototype;
@@ -228,11 +228,17 @@ describe(replaceEqualDeep, () => {
228
228
  });
229
229
 
230
230
  test('compares a Map, a Set, a Date or a class instance whole', () => {
231
- const map = new Map([['a', { x: 1 }]]);
231
+ const map = new Map<string, Readonly<{ x: number }>>([['a', { x: 1 }]]);
232
232
 
233
- assert.strictEqual(replaceEqualDeep(map, new Map([['a', { x: 1 }]])), map);
233
+ assert.strictEqual(
234
+ replaceEqualDeep(
235
+ map,
236
+ new Map<string, Readonly<{ x: number }>>([['a', { x: 1 }]]),
237
+ ),
238
+ map,
239
+ );
234
240
 
235
- const changedMap = new Map([
241
+ const changedMap = new Map<string, Readonly<{ x: number }>>([
236
242
  ['a', { x: 1 }],
237
243
  ['b', { x: 2 }],
238
244
  ]);
@@ -244,9 +250,9 @@ describe(replaceEqualDeep, () => {
244
250
  // Nothing inside is reused.
245
251
  assert.notStrictEqual(sharedMap.get('a'), map.get('a'));
246
252
 
247
- const set = new Set([1, 2]);
253
+ const set = new Set<number>([1, 2]);
248
254
 
249
- assert.strictEqual(replaceEqualDeep(set, new Set([1, 2])), set);
255
+ assert.strictEqual(replaceEqualDeep(set, new Set<number>([1, 2])), set);
250
256
 
251
257
  const date = new Date(0);
252
258
 
@@ -282,12 +288,12 @@ describe(replaceEqualDeep, () => {
282
288
  });
283
289
 
284
290
  test('reuses a Map compared whole inside a plain object', () => {
285
- const map = new Map([['a', 1]]);
291
+ const map = new Map<string, number>([['a', 1]]);
286
292
 
287
293
  const current = { map, count: 1 } as const;
288
294
 
289
295
  const shared = replaceEqualDeep(current, {
290
- map: new Map([['a', 1]]),
296
+ map: new Map<string, number>([['a', 1]]),
291
297
  count: 2,
292
298
  });
293
299
 
@@ -522,8 +522,8 @@ export namespace Str {
522
522
  * `NonEmptyString` is an alias of `MinLengthString<1>`, so this is the
523
523
  * length-1 specialization of {@link asMinLengthString}. Use this instead of
524
524
  * writing `s as unknown as NonEmptyString`: the length is verified at
525
- * runtime, and the original string type (e.g. literal types) is preserved
526
- * via intersection.
525
+ * runtime, and the original string type (e.g. literal types) is preserved via
526
+ * intersection.
527
527
  *
528
528
  * @example
529
529
  *