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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-data-forge",
3
- "version": "6.5.0",
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.7",
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.3",
100
- "@semantic-release/npm": "13.1.3",
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.2.0",
103
- "@types/react": "19.2.13",
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.1.0",
108
- "cspell": "9.6.4",
109
- "dedent": "^1.7.1",
110
- "eslint": "9.39.2",
111
- "eslint-config-typed": "4.6.2",
112
- "github-settings-as-code": "1.1.2",
113
- "immer": "11.1.3",
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.20.0",
116
+ "markdownlint-cli2": "0.21.0",
117
117
  "npm-run-all2": "8.0.4",
118
- "playwright": "1.58.1",
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.0",
121
+ "prettier-plugin-packagejson": "3.0.2",
122
122
  "react": "^19.2.4",
123
- "rollup": "4.57.1",
123
+ "rollup": "4.59.0",
124
124
  "semantic-release": "25.0.3",
125
- "ts-codemod-lib": "^2.0.3",
126
- "ts-repo-utils": "8.1.0",
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.16",
130
- "typedoc-github-theme": "0.3.1",
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.28.2",
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>);
@@ -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>);
@@ -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 type predicate. Narrows the type
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 type predicate function.
258
- * @returns A new ISet instance with elements that satisfy the type predicate.
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
  /**
@@ -306,77 +306,197 @@ export namespace Obj {
306
306
  Object.fromEntries(entries) as never;
307
307
 
308
308
  /**
309
- * @internal
310
- * Internal type utilities for the Obj module.
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
- declare namespace TsDataForgeInternals {
313
- type RecursionLimit = 20;
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
- /** - `[['x', 1], ['y', 3]]` -> `{ x: 1, y: 3 }` */
316
- export type EntriesToObject<
317
- Entries extends readonly (readonly [PropertyKey, unknown])[],
318
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
319
- > = Readonly<EntriesToObjectImpl<{}, Entries>>;
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
- /** @internal */
322
- type EntriesToObjectImpl<
323
- R,
324
- Entries extends readonly (readonly [PropertyKey, unknown])[],
325
- > =
326
- TypeEq<Entries['length'], 0> extends true
327
- ? R
328
- : EntriesToObjectImpl<
329
- R & Readonly<Record<Entries[0][0], Entries[0][1]>>,
330
- List.Tail<Entries>
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
- * - `['x' | 'y' | 'z', number][]]` -> `'x' | 'y' | 'z'`
335
- * - `[['a' | 'b' | 'c', number], ...['x' | 'y' | 'z', number][]]` -> `'a' |
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
- type KeysOfEntriesImpl<
346
- K,
347
- Entries extends readonly (readonly [PropertyKey, unknown])[],
348
- RemainingNumRecursions extends number,
349
- > =
350
- TypeEq<RemainingNumRecursions, 0> extends true
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
- : TypeEq<Entries['length'], 0> extends true
353
- ? K
354
- : KeysOfEntriesImpl<
355
- K | Entries[0][0],
356
- List.Tail<Entries>,
357
- Decrement<RemainingNumRecursions>
358
- >;
398
+ : ValuesOfEntriesImpl<
399
+ K | Entries[0][1],
400
+ List.Tail<Entries>,
401
+ Decrement<RemainingNumRecursions>
402
+ >;
359
403
 
360
- export type ValuesOfEntries<
361
- Entries extends readonly (readonly [PropertyKey, unknown])[],
362
- > = ValuesOfEntriesImpl<never, Entries, RecursionLimit>;
404
+ export type PartialIfKeyIsUnion<K, T> =
405
+ IsUnion<K> extends true ? Partial<T> : T;
363
406
 
364
- type ValuesOfEntriesImpl<
365
- K,
366
- Entries extends readonly (readonly [PropertyKey, unknown])[],
367
- RemainingNumRecursions extends number,
368
- > =
369
- TypeEq<RemainingNumRecursions, 0> extends true
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
- : TypeEq<Entries['length'], 0> extends true
372
- ? K
373
- : ValuesOfEntriesImpl<
374
- K | Entries[0][1],
375
- List.Tail<Entries>,
376
- Decrement<RemainingNumRecursions>
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
- export type PartialIfKeyIsUnion<K, T> =
380
- IsUnion<K> extends true ? Partial<T> : T;
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
  }