Skip to content

Commit b9723d4

Browse files
Emiyaaaaasindresorhus
andauthoredOct 26, 2023
Writable: Support array, map, and set (#726)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent 964466c commit b9723d4

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed
 

‎readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Click the type names for complete docs.
114114
- [`NonEmptyObject`](source/non-empty-object.d.ts) - Represents an object with at least 1 non-optional key.
115115
- [`UnknownRecord`](source/unknown-record.d.ts) - Represents an object with `unknown` value. You probably want this instead of `{}`.
116116
- [`Except`](source/except.d.ts) - Create a type from an object type without certain keys. This is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys).
117-
- [`Writable`](source/writable.d.ts) - Create a type that strips `readonly` from all or some of an object's keys. The inverse of `Readonly<T>`.
117+
- [`Writable`](source/writable.d.ts) - Create a type that strips `readonly` from the given type. Inverse of `Readonly<T>`.
118118
- [`WritableDeep`](source/writable-deep.d.ts) - Create a deeply mutable version of an `object`/`ReadonlyMap`/`ReadonlySet`/`ReadonlyArray` type. The inverse of `ReadonlyDeep<T>`. Use `Writable<T>` if you only need one level deep.
119119
- [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type.
120120
- [`MergeDeep`](source/merge-deep.d.ts) - Merge two objects or two arrays/tuples recursively into a new type.

‎source/writable.d.ts

+35-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@ import type {Except} from './except';
22
import type {Simplify} from './simplify';
33

44
/**
5-
Create a type that strips `readonly` from all or some of an object's keys. Inverse of `Readonly<T>`.
5+
Create a writable version of the given array type.
6+
*/
7+
type WritableArray<ArrayType extends readonly unknown[]> =
8+
ArrayType extends readonly [] ? []
9+
: ArrayType extends readonly [...infer U, infer V] ? [...U, V]
10+
: ArrayType extends readonly [infer U, ...infer V] ? [U, ...V]
11+
: ArrayType extends ReadonlyArray<infer U> ? U[]
12+
: ArrayType;
13+
14+
/**
15+
Create a type that strips `readonly` from the given type. Inverse of `Readonly<T>`.
16+
17+
The 2nd argument will be ignored if the input type is not an object.
18+
19+
Note: This type can make readonly `Set` and `Map` writable. This behavior is different from `Readonly<T>` (as of TypeScript 5.2.2). See: https://github.com/microsoft/TypeScript/issues/29655
620
721
This can be used to [store and mutate options within a class](https://github.com/sindresorhus/pageres/blob/4a5d05fca19a5fbd2f53842cbf3eb7b1b63bddd2/source/index.ts#L72), [edit `readonly` objects within tests](https://stackoverflow.com/questions/50703834), [construct a `readonly` object within a function](https://github.com/Microsoft/TypeScript/issues/24509), or to define a single model where the only thing that changes is whether or not some of the keys are writable.
822
@@ -27,14 +41,28 @@ type SomeWritable = Writable<Foo, 'b' | 'c'>;
2741
// b: readonly string[]; // It's now writable. The type of the property remains unaffected.
2842
// c: boolean; // It's now writable.
2943
// }
44+
45+
// Also supports array
46+
const readonlyArray: readonly number[] = [1, 2, 3];
47+
readonlyArray.push(4); // Will fail as the array itself is readonly.
48+
const writableArray: Writable<typeof readonlyArray> = readonlyArray as Writable<typeof readonlyArray>;
49+
writableArray.push(4); // Will work as the array itself is now writable.
3050
```
3151
3252
@category Object
3353
*/
3454
export type Writable<BaseType, Keys extends keyof BaseType = keyof BaseType> =
35-
Simplify<
36-
// Pick just the keys that are not writable from the base type.
37-
Except<BaseType, Keys> &
38-
// Pick the keys that should be writable from the base type and make them writable by removing the `readonly` modifier from the key.
39-
{-readonly [KeyType in keyof Pick<BaseType, Keys>]: Pick<BaseType, Keys>[KeyType]}
40-
>;
55+
BaseType extends ReadonlyMap<infer KeyType, infer ValueType>
56+
? Map<KeyType, ValueType>
57+
: BaseType extends ReadonlySet<infer ItemType>
58+
? Set<ItemType>
59+
: BaseType extends readonly unknown[]
60+
// Handle array
61+
? WritableArray<BaseType>
62+
// Handle object
63+
: Simplify<
64+
// Pick just the keys that are not writable from the base type.
65+
Except<BaseType, Keys> &
66+
// Pick the keys that should be writable from the base type and make them writable by removing the `readonly` modifier from the key.
67+
{-readonly [KeyType in keyof Pick<BaseType, Keys>]: Pick<BaseType, Keys>[KeyType]}
68+
>;

‎test-d/writable.ts

+22
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,25 @@ expectType<{a: number; b: string; c: boolean}>(variation3);
2626
// Check if type changes raise an error even if readonly and writable are applied correctly.
2727
declare const variation4: Writable<{readonly a: number; b: string; readonly c: boolean}, 'b' | 'c'>;
2828
expectNotAssignable<{readonly a: boolean; b: string; c: boolean}>(variation4);
29+
30+
// Test array
31+
declare const variation5: Writable<readonly string[]>;
32+
expectType<string[]>(variation5);
33+
34+
// Test tuple
35+
declare const variation8: Writable<readonly [string, number]>;
36+
expectType<[string, number]>(variation8);
37+
38+
// Test tuple with spread
39+
declare const variation6: Writable<readonly [...string[], number]>;
40+
expectType<[...string[], number]>(variation6);
41+
declare const variation7: Writable<readonly [string, ...number[]]>;
42+
expectType<[string, ...number[]]>(variation7);
43+
44+
// Test readonly set
45+
declare const variation9: Writable<ReadonlySet<string>>;
46+
expectType<Set<string>>(variation9);
47+
48+
// Test readonly map
49+
declare const variation10: Writable<ReadonlyMap<string, number>>;
50+
expectType<Map<string, number>>(variation10);

0 commit comments

Comments
 (0)
Please sign in to comment.