ts-data-forge 6.5.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/imap-mapped.d.mts +5 -12
- package/dist/collections/imap-mapped.d.mts.map +1 -1
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.d.mts +5 -12
- package/dist/collections/imap.d.mts.map +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +6 -12
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +8 -22
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/object/object.d.mts +104 -29
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +21 -0
- package/dist/object/object.mjs.map +1 -1
- package/package.json +22 -22
- package/src/collections/imap-mapped.mts +4 -10
- package/src/collections/imap.mts +4 -10
- package/src/collections/iset-mapped.mts +5 -10
- package/src/collections/iset.mts +8 -20
- package/src/object/object.mts +182 -62
- package/src/object/object.test.mts +291 -0
- package/src/others/cast-readonly.test.mts +2 -0
- package/src/others/fast-deep-equal.test.mts +7 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-data-forge",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@emotion/react": "11.14.0",
|
|
90
90
|
"@emotion/styled": "11.14.1",
|
|
91
|
-
"@mui/material": "7.3.
|
|
91
|
+
"@mui/material": "7.3.9",
|
|
92
92
|
"@rollup/plugin-replace": "6.0.3",
|
|
93
93
|
"@rollup/plugin-strip": "3.0.4",
|
|
94
94
|
"@rollup/plugin-typescript": "12.3.0",
|
|
@@ -96,38 +96,38 @@
|
|
|
96
96
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
97
97
|
"@semantic-release/exec": "7.1.0",
|
|
98
98
|
"@semantic-release/git": "10.0.1",
|
|
99
|
-
"@semantic-release/github": "12.0.
|
|
100
|
-
"@semantic-release/npm": "13.1.
|
|
99
|
+
"@semantic-release/github": "12.0.6",
|
|
100
|
+
"@semantic-release/npm": "13.1.5",
|
|
101
101
|
"@semantic-release/release-notes-generator": "14.1.0",
|
|
102
|
-
"@types/node": "25.
|
|
103
|
-
"@types/react": "19.2.
|
|
102
|
+
"@types/node": "25.4.0",
|
|
103
|
+
"@types/react": "19.2.14",
|
|
104
104
|
"@vitest/browser-playwright": "4.0.18",
|
|
105
105
|
"@vitest/coverage-v8": "4.0.18",
|
|
106
106
|
"@vitest/ui": "4.0.18",
|
|
107
|
-
"conventional-changelog-conventionalcommits": "9.
|
|
108
|
-
"cspell": "9.
|
|
109
|
-
"dedent": "^1.7.
|
|
110
|
-
"eslint": "9.39.
|
|
111
|
-
"eslint-config-typed": "4.
|
|
112
|
-
"github-settings-as-code": "1.
|
|
113
|
-
"immer": "11.1.
|
|
107
|
+
"conventional-changelog-conventionalcommits": "9.3.0",
|
|
108
|
+
"cspell": "9.7.0",
|
|
109
|
+
"dedent": "^1.7.2",
|
|
110
|
+
"eslint": "9.39.3",
|
|
111
|
+
"eslint-config-typed": "4.7.4",
|
|
112
|
+
"github-settings-as-code": "1.2.0",
|
|
113
|
+
"immer": "11.1.4",
|
|
114
114
|
"jiti": "2.6.1",
|
|
115
115
|
"markdownlint": "0.40.0",
|
|
116
|
-
"markdownlint-cli2": "0.
|
|
116
|
+
"markdownlint-cli2": "0.21.0",
|
|
117
117
|
"npm-run-all2": "8.0.4",
|
|
118
|
-
"playwright": "1.58.
|
|
118
|
+
"playwright": "1.58.2",
|
|
119
119
|
"prettier": "3.8.1",
|
|
120
120
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
121
|
-
"prettier-plugin-packagejson": "3.0.
|
|
121
|
+
"prettier-plugin-packagejson": "3.0.2",
|
|
122
122
|
"react": "^19.2.4",
|
|
123
|
-
"rollup": "4.
|
|
123
|
+
"rollup": "4.59.0",
|
|
124
124
|
"semantic-release": "25.0.3",
|
|
125
|
-
"ts-codemod-lib": "^2.
|
|
126
|
-
"ts-repo-utils": "
|
|
125
|
+
"ts-codemod-lib": "^2.1.1",
|
|
126
|
+
"ts-repo-utils": "9.0.0",
|
|
127
127
|
"tslib": "2.8.1",
|
|
128
128
|
"tsx": "4.21.0",
|
|
129
|
-
"typedoc": "0.28.
|
|
130
|
-
"typedoc-github-theme": "0.
|
|
129
|
+
"typedoc": "0.28.17",
|
|
130
|
+
"typedoc-github-theme": "0.4.0",
|
|
131
131
|
"typescript": "5.9.3",
|
|
132
132
|
"vite": "7.3.1",
|
|
133
133
|
"vitest": "4.0.18"
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"peerDependencies": {
|
|
136
136
|
"typescript": ">=4.8"
|
|
137
137
|
},
|
|
138
|
-
"packageManager": "pnpm@10.
|
|
138
|
+
"packageManager": "pnpm@10.32.1",
|
|
139
139
|
"engines": {
|
|
140
140
|
"node": ">=20.11.0",
|
|
141
141
|
"pnpm": ">=8.0.0"
|
|
@@ -56,21 +56,15 @@ type IMapMappedInterface<K, V, KM extends MapSetKeyType> = Readonly<{
|
|
|
56
56
|
// Reducing a value
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Checks if all elements in the map satisfy a predicate.
|
|
59
|
+
* Checks if all elements in the map satisfy a predicate. Also supports a
|
|
60
|
+
* type predicate overload that narrows the type of values in the map if the
|
|
61
|
+
* predicate returns true for all elements.
|
|
60
62
|
*
|
|
63
|
+
* @template W The narrowed type of the values.
|
|
61
64
|
* @param predicate A function to test each key-value pair.
|
|
62
65
|
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
63
66
|
*/
|
|
64
67
|
every: ((predicate: (value: V, key: K) => boolean) => boolean) &
|
|
65
|
-
/**
|
|
66
|
-
* Checks if all elements in the map satisfy a type predicate. Narrows the
|
|
67
|
-
* type of values in the map if the predicate returns true for all
|
|
68
|
-
* elements.
|
|
69
|
-
*
|
|
70
|
-
* @template W The narrowed type of the values.
|
|
71
|
-
* @param predicate A type predicate function.
|
|
72
|
-
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
73
|
-
*/
|
|
74
68
|
(<W extends V>(
|
|
75
69
|
predicate: (value: V, key: K) => value is W,
|
|
76
70
|
) => this is IMapMapped<K, W, KM>);
|
package/src/collections/imap.mts
CHANGED
|
@@ -93,7 +93,9 @@ type IMapInterface<K extends MapSetKeyType, V> = Readonly<{
|
|
|
93
93
|
// Reducing a value
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* Checks if all elements in the map satisfy a predicate.
|
|
96
|
+
* Checks if all elements in the map satisfy a predicate. Also supports a
|
|
97
|
+
* type predicate overload that narrows the type of values in the map if the
|
|
98
|
+
* predicate returns true for all elements.
|
|
97
99
|
*
|
|
98
100
|
* @example
|
|
99
101
|
*
|
|
@@ -112,19 +114,11 @@ type IMapInterface<K extends MapSetKeyType, V> = Readonly<{
|
|
|
112
114
|
* assert.isTrue(isNarrowed);
|
|
113
115
|
* ```
|
|
114
116
|
*
|
|
117
|
+
* @template W The narrowed type of the values.
|
|
115
118
|
* @param predicate A function to test each key-value pair.
|
|
116
119
|
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
117
120
|
*/
|
|
118
121
|
every: ((predicate: (value: V, key: K) => boolean) => boolean) &
|
|
119
|
-
/**
|
|
120
|
-
* Checks if all elements in the map satisfy a type predicate. Narrows the
|
|
121
|
-
* type of values in the map if the predicate returns true for all
|
|
122
|
-
* elements.
|
|
123
|
-
*
|
|
124
|
-
* @template W The narrowed type of the values.
|
|
125
|
-
* @param predicate A type predicate function.
|
|
126
|
-
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
127
|
-
*/
|
|
128
122
|
(<W extends V>(
|
|
129
123
|
predicate: (value: V, key: K) => value is W,
|
|
130
124
|
) => this is IMap<K, W>);
|
|
@@ -129,7 +129,10 @@ type ISetMappedInterface<K, KM extends MapSetKeyType> = Readonly<{
|
|
|
129
129
|
// Reducing a value
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Checks if all elements in the set satisfy a predicate.
|
|
132
|
+
* Checks if all elements in the set satisfy a type predicate. Narrows the
|
|
133
|
+
* type of elements in the set if the predicate returns true for all
|
|
134
|
+
* elements.
|
|
135
|
+
*
|
|
133
136
|
*
|
|
134
137
|
* @example
|
|
135
138
|
*
|
|
@@ -162,19 +165,11 @@ type ISetMappedInterface<K, KM extends MapSetKeyType> = Readonly<{
|
|
|
162
165
|
* assert.isTrue(narrowed);
|
|
163
166
|
* ```
|
|
164
167
|
*
|
|
168
|
+
* @template L The narrowed type of the elements.
|
|
165
169
|
* @param predicate A function to test each element.
|
|
166
170
|
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
167
171
|
*/
|
|
168
172
|
every: ((predicate: (key: K) => boolean) => boolean) &
|
|
169
|
-
/**
|
|
170
|
-
* Checks if all elements in the set satisfy a type predicate. Narrows the
|
|
171
|
-
* type of elements in the set if the predicate returns true for all
|
|
172
|
-
* elements.
|
|
173
|
-
*
|
|
174
|
-
* @template L The narrowed type of the elements.
|
|
175
|
-
* @param predicate A type predicate function.
|
|
176
|
-
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
177
|
-
*/
|
|
178
173
|
(<L extends K>(
|
|
179
174
|
predicate: (key: K) => key is L,
|
|
180
175
|
) => this is ISetMapped<L, KM>);
|
package/src/collections/iset.mts
CHANGED
|
@@ -83,7 +83,9 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
|
83
83
|
// Reducing a value
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
* Checks if all elements in the set satisfy a predicate.
|
|
86
|
+
* Checks if all elements in the set satisfy a predicate. Also supports a
|
|
87
|
+
* type predicate overload that narrows the type of elements in the set if
|
|
88
|
+
* the predicate returns true for all elements.
|
|
87
89
|
*
|
|
88
90
|
* @example
|
|
89
91
|
*
|
|
@@ -101,19 +103,11 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
|
101
103
|
* assert.isTrue(narrowed);
|
|
102
104
|
* ```
|
|
103
105
|
*
|
|
106
|
+
* @template L The narrowed type of the elements.
|
|
104
107
|
* @param predicate A function to test each element.
|
|
105
108
|
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
106
109
|
*/
|
|
107
110
|
every: ((predicate: (key: K) => boolean) => boolean) &
|
|
108
|
-
/**
|
|
109
|
-
* Checks if all elements in the set satisfy a type predicate. Narrows the
|
|
110
|
-
* type of elements in the set if the predicate returns true for all
|
|
111
|
-
* elements.
|
|
112
|
-
*
|
|
113
|
-
* @template L The narrowed type of the elements.
|
|
114
|
-
* @param predicate A type predicate function.
|
|
115
|
-
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
116
|
-
*/
|
|
117
111
|
(<L extends K>(predicate: (key: K) => key is L) => this is ISet<L>);
|
|
118
112
|
|
|
119
113
|
/**
|
|
@@ -234,8 +228,8 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
|
234
228
|
map: <K2 extends MapSetKeyType>(mapFn: (key: K) => K2) => ISet<K2>;
|
|
235
229
|
|
|
236
230
|
/**
|
|
237
|
-
* Filters the elements of the set based on a
|
|
238
|
-
* of elements in the resulting set.
|
|
231
|
+
* Filters the elements of the set based on a predicate. Also supports a type
|
|
232
|
+
* predicate overload that narrows the type of elements in the resulting set.
|
|
239
233
|
*
|
|
240
234
|
* @example
|
|
241
235
|
*
|
|
@@ -254,16 +248,10 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
|
254
248
|
* ```
|
|
255
249
|
*
|
|
256
250
|
* @template K2 The narrowed type of the elements.
|
|
257
|
-
* @param predicate A
|
|
258
|
-
* @returns A new ISet instance with elements that satisfy the
|
|
251
|
+
* @param predicate A function to test each element.
|
|
252
|
+
* @returns A new ISet instance with elements that satisfy the predicate.
|
|
259
253
|
*/
|
|
260
254
|
filter: (<K2 extends K>(predicate: (key: K) => key is K2) => ISet<K2>) &
|
|
261
|
-
/**
|
|
262
|
-
* Filters the elements of the set based on a predicate.
|
|
263
|
-
*
|
|
264
|
-
* @param predicate A function to test each element.
|
|
265
|
-
* @returns A new ISet instance with elements that satisfy the predicate.
|
|
266
|
-
*/
|
|
267
255
|
((predicate: (key: K) => boolean) => ISet<K>);
|
|
268
256
|
|
|
269
257
|
/**
|
package/src/object/object.mts
CHANGED
|
@@ -306,77 +306,197 @@ export namespace Obj {
|
|
|
306
306
|
Object.fromEntries(entries) as never;
|
|
307
307
|
|
|
308
308
|
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
309
|
+
* Merges multiple records into a single record using `Object.assign`.
|
|
310
|
+
* Later records override properties from earlier records with the same key.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
*
|
|
314
|
+
* ```ts
|
|
315
|
+
* const a = { a: 0, b: 0 } as const;
|
|
316
|
+
* const b = { b: 1, c: 0 } as const;
|
|
317
|
+
*
|
|
318
|
+
* const result = Obj.merge(a, b);
|
|
319
|
+
*
|
|
320
|
+
* assert.deepStrictEqual(result, { a: 0, b: 1, c: 0 });
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* @param records - The records to merge
|
|
324
|
+
* @returns A new record with all properties merged
|
|
311
325
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
326
|
+
export const merge = <const Records extends readonly UnknownRecord[]>(
|
|
327
|
+
...records: Records
|
|
328
|
+
): TsDataForgeInternals.MergeAll<Records> =>
|
|
329
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
330
|
+
Object.fromEntries(records.flatMap((r) => Object.entries(r))) as never;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @internal
|
|
335
|
+
* Internal type utilities for the Obj module.
|
|
336
|
+
*/
|
|
337
|
+
declare namespace TsDataForgeInternals {
|
|
338
|
+
type RecursionLimit = 20;
|
|
339
|
+
|
|
340
|
+
/** - `[['x', 1], ['y', 3]]` -> `{ x: 1, y: 3 }` */
|
|
341
|
+
export type EntriesToObject<
|
|
342
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
344
|
+
> = Readonly<EntriesToObjectImpl<{}, Entries>>;
|
|
314
345
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
346
|
+
/** @internal */
|
|
347
|
+
type EntriesToObjectImpl<
|
|
348
|
+
R,
|
|
349
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
350
|
+
> =
|
|
351
|
+
TypeEq<Entries['length'], 0> extends true
|
|
352
|
+
? R
|
|
353
|
+
: EntriesToObjectImpl<
|
|
354
|
+
R & Readonly<Record<Entries[0][0], Entries[0][1]>>,
|
|
355
|
+
List.Tail<Entries>
|
|
356
|
+
>;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* - `['x' | 'y' | 'z', number][]]` -> `'x' | 'y' | 'z'`
|
|
360
|
+
* - `[['a' | 'b' | 'c', number], ...['x' | 'y' | 'z', number][]]` -> `'a' |
|
|
361
|
+
* 'b' | 'c' | 'x' | 'y' | 'z'`
|
|
362
|
+
*
|
|
363
|
+
* @note To handle the second example above, recursion needs to be performed on infinite-length Entries,
|
|
364
|
+
* but since the timing to stop cannot be determined, a recursion limit is set.
|
|
365
|
+
*/
|
|
366
|
+
export type KeysOfEntries<
|
|
367
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
368
|
+
> = KeysOfEntriesImpl<never, Entries, RecursionLimit>;
|
|
320
369
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
370
|
+
type KeysOfEntriesImpl<
|
|
371
|
+
K,
|
|
372
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
373
|
+
RemainingNumRecursions extends number,
|
|
374
|
+
> =
|
|
375
|
+
TypeEq<RemainingNumRecursions, 0> extends true
|
|
376
|
+
? K
|
|
377
|
+
: TypeEq<Entries['length'], 0> extends true
|
|
378
|
+
? K
|
|
379
|
+
: KeysOfEntriesImpl<
|
|
380
|
+
K | Entries[0][0],
|
|
381
|
+
List.Tail<Entries>,
|
|
382
|
+
Decrement<RemainingNumRecursions>
|
|
331
383
|
>;
|
|
332
384
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
* 'b' | 'c' | 'x' | 'y' | 'z'`
|
|
337
|
-
*
|
|
338
|
-
* @note To handle the second example above, recursion needs to be performed on infinite-length Entries,
|
|
339
|
-
* but since the timing to stop cannot be determined, a recursion limit is set.
|
|
340
|
-
*/
|
|
341
|
-
export type KeysOfEntries<
|
|
342
|
-
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
343
|
-
> = KeysOfEntriesImpl<never, Entries, RecursionLimit>;
|
|
385
|
+
export type ValuesOfEntries<
|
|
386
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
387
|
+
> = ValuesOfEntriesImpl<never, Entries, RecursionLimit>;
|
|
344
388
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
389
|
+
type ValuesOfEntriesImpl<
|
|
390
|
+
K,
|
|
391
|
+
Entries extends readonly (readonly [PropertyKey, unknown])[],
|
|
392
|
+
RemainingNumRecursions extends number,
|
|
393
|
+
> =
|
|
394
|
+
TypeEq<RemainingNumRecursions, 0> extends true
|
|
395
|
+
? K
|
|
396
|
+
: TypeEq<Entries['length'], 0> extends true
|
|
351
397
|
? K
|
|
352
|
-
:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
Decrement<RemainingNumRecursions>
|
|
358
|
-
>;
|
|
398
|
+
: ValuesOfEntriesImpl<
|
|
399
|
+
K | Entries[0][1],
|
|
400
|
+
List.Tail<Entries>,
|
|
401
|
+
Decrement<RemainingNumRecursions>
|
|
402
|
+
>;
|
|
359
403
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
> = ValuesOfEntriesImpl<never, Entries, RecursionLimit>;
|
|
404
|
+
export type PartialIfKeyIsUnion<K, T> =
|
|
405
|
+
IsUnion<K> extends true ? Partial<T> : T;
|
|
363
406
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Merges two object types where keys in B override keys in A, preserving optional modifiers.
|
|
409
|
+
*
|
|
410
|
+
* This implementation uses a sophisticated technique to preserve optional property modifiers (`?`)
|
|
411
|
+
* during the merge operation, which would otherwise be lost in a naive mapped type implementation.
|
|
412
|
+
*
|
|
413
|
+
* ## Core Technique: Optional Property Detection
|
|
414
|
+
*
|
|
415
|
+
* Uses `{} extends Pick<T, K>` to determine if a property is optional:
|
|
416
|
+
* - **Required property**: `Pick<T, 'x'>` = `{ x: number }` → `{}` does NOT extend it (false)
|
|
417
|
+
* - **Optional property**: `Pick<T, 'y'>` = `{ y?: string }` → `{}` DOES extend it (true)
|
|
418
|
+
*
|
|
419
|
+
* This works because `{}` (empty object) is assignable to objects with only optional properties
|
|
420
|
+
* but not to objects with required properties.
|
|
421
|
+
*
|
|
422
|
+
* ## Implementation Strategy
|
|
423
|
+
*
|
|
424
|
+
* The type is constructed as an intersection of five mapped types:
|
|
425
|
+
*
|
|
426
|
+
* 1. **Required properties from B** - Properties in B that are required (override A completely)
|
|
427
|
+
* 2. **Optional properties from B (not in A)** - New optional properties from B
|
|
428
|
+
* 3. **Optional properties from B (in A)** - Union of A[K] | B[K] to preserve both possibilities
|
|
429
|
+
* 4. **Required properties only in A** - Properties in A but not in B that are required
|
|
430
|
+
* 5. **Optional properties only in A** - Properties in A but not in B that are optional (with `?` modifier)
|
|
431
|
+
*
|
|
432
|
+
* Finally, the intersection is flattened using an optional-preserving identity mapping that
|
|
433
|
+
* re-applies the optional detection logic to maintain the `?` modifiers in the final type.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* type A = { x: number; y?: string };
|
|
438
|
+
* type B = { y?: number; z?: boolean };
|
|
439
|
+
* type Result = MergeTwo<A, B>;
|
|
440
|
+
* // Result: { x: number; y?: string | number; z?: boolean }
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
// transformer-ignore-next-line
|
|
444
|
+
type MergeTwo<A extends UnknownRecord, B extends UnknownRecord> = {
|
|
445
|
+
// 1. Required properties from B (override A completely)
|
|
446
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
447
|
+
readonly [K in keyof B as {} extends Pick<B, K> ? never : K]: B[K];
|
|
448
|
+
} & {
|
|
449
|
+
// 2. Optional properties from B that are NOT in A
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
451
|
+
readonly [K in keyof B as {} extends Pick<B, K>
|
|
452
|
+
? K extends keyof A
|
|
453
|
+
? never
|
|
454
|
+
: K
|
|
455
|
+
: never]?: B[K];
|
|
456
|
+
} & {
|
|
457
|
+
// 3. Optional properties from B that ARE in A (create union)
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
459
|
+
readonly [K in keyof B as {} extends Pick<B, K>
|
|
460
|
+
? K extends keyof A
|
|
370
461
|
? K
|
|
371
|
-
:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
462
|
+
: never
|
|
463
|
+
: never]?: K extends keyof A ? A[K] | B[K] : never;
|
|
464
|
+
} & {
|
|
465
|
+
// 4. Required properties only in A (not overridden by B)
|
|
466
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
467
|
+
readonly [K in Exclude<keyof A, keyof B> as {} extends Pick<A, K>
|
|
468
|
+
? never
|
|
469
|
+
: K]: A[K];
|
|
470
|
+
} & {
|
|
471
|
+
// 5. Optional properties only in A (not overridden by B)
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
473
|
+
readonly [K in Exclude<keyof A, keyof B> as {} extends Pick<A, K>
|
|
474
|
+
? K
|
|
475
|
+
: never]?: A[K];
|
|
476
|
+
} extends infer O
|
|
477
|
+
? Readonly<
|
|
478
|
+
{
|
|
479
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
480
|
+
[K in keyof O as {} extends Pick<O, K> ? never : K]: O[K];
|
|
481
|
+
} & {
|
|
482
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
483
|
+
[K in keyof O as {} extends Pick<O, K> ? K : never]?: O[K];
|
|
484
|
+
}
|
|
485
|
+
>
|
|
486
|
+
: never;
|
|
378
487
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
488
|
+
/** Sequentially merges a tuple of object types from left to right. */
|
|
489
|
+
export type MergeAll<
|
|
490
|
+
Records extends readonly UnknownRecord[],
|
|
491
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
492
|
+
Acc extends UnknownRecord = {},
|
|
493
|
+
> =
|
|
494
|
+
IsFixedLengthList<Records> extends false
|
|
495
|
+
? ArrayElement<Records>
|
|
496
|
+
: Records extends readonly [
|
|
497
|
+
infer First extends UnknownRecord,
|
|
498
|
+
...infer Rest extends readonly UnknownRecord[],
|
|
499
|
+
]
|
|
500
|
+
? MergeAll<Rest, MergeTwo<Acc, First>>
|
|
501
|
+
: Acc;
|
|
382
502
|
}
|