diff --git a/readme.md b/readme.md index df5b0e7ff..810386c95 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,7 @@ Click the type names for complete docs. ### Utilities - [`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/release-notes/typescript-3-5.html#the-omit-helper-type). -- [`Mutable`](source/mutable.d.ts) - Convert an object with `readonly` keys into a mutable object. The inverse of `Readonly`. +- [`Mutable`](source/mutable.d.ts) - Create a type that strips `readonly` from all or some of an object's keys. The inverse of `Readonly`. - [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type. - [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive keys. - [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys. diff --git a/source/mutable.d.ts b/source/mutable.d.ts index 03d0dda7f..a465d4132 100644 --- a/source/mutable.d.ts +++ b/source/mutable.d.ts @@ -1,7 +1,10 @@ +import {Except} from './except'; +import {Simplify} from './simplify'; + /** -Convert an object with `readonly` keys into a mutable object. Inverse of `Readonly`. +Create a type that strips `readonly` from all or some of an object's keys. Inverse of `Readonly`. -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), and [construct a `readonly` object within a function](https://github.com/Microsoft/TypeScript/issues/24509). +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 mutable. @example ``` @@ -9,14 +12,27 @@ import {Mutable} from 'type-fest'; type Foo = { readonly a: number; - readonly b: string; + readonly b: readonly string[]; // To show that only the mutability status of the properties, not their values, are affected. + readonly c: boolean; }; -const mutableFoo: Mutable = {a: 1, b: '2'}; +const mutableFoo: Mutable = {a: 1, b: ['2']}; mutableFoo.a = 3; +mutableFoo.b[0] = 'new value'; // Will still fail as the value of property "b" is still a readonly type. +mutableFoo.b = ['something']; // Will work as the "b" property itself is no longer readonly. + +type SomeMutable = Mutable; +// type SomeMutable = { +// readonly a: number; +// b: readonly string[]; // It's now mutable. The type of the property remains unaffected. +// c: boolean; // It's now mutable. +// } ``` */ -export type Mutable = { - // For each `Key` in the keys of `ObjectType`, make a mapped type by removing the `readonly` modifier from the key. - -readonly [KeyType in keyof ObjectType]: ObjectType[KeyType]; -}; +export type Mutable = + Simplify< + // Pick just the keys that are not mutable from the base type. + Except & + // Pick the keys that should be mutable from the base type and make them mutable by removing the `readonly` modifier from the key. + {-readonly [KeyType in keyof Pick]: Pick[KeyType]} + >; diff --git a/source/set-optional.d.ts b/source/set-optional.d.ts index 35398992b..ebaf09819 100644 --- a/source/set-optional.d.ts +++ b/source/set-optional.d.ts @@ -1,4 +1,5 @@ import {Except} from './except'; +import {Simplify} from './simplify'; /** Create a type that makes the given keys optional. The remaining keys are kept as is. The sister of the `SetRequired` type. @@ -23,12 +24,10 @@ type SomeOptional = SetOptional; // } ``` */ -export type SetOptional = - // Pick just the keys that are not optional from the base type. - Except & - // Pick the keys that should be optional from the base type and make them optional. - Partial> extends - // If `InferredType` extends the previous, then for each key, use the inferred type key. - infer InferredType - ? {[KeyType in keyof InferredType]: InferredType[KeyType]} - : never; +export type SetOptional = + Simplify< + // Pick just the keys that are readonly from the base type. + Except & + // Pick the keys that should be mutable from the base type and make them mutable. + Partial> + >; diff --git a/source/set-required.d.ts b/source/set-required.d.ts index 0a7233077..ab8593eb5 100644 --- a/source/set-required.d.ts +++ b/source/set-required.d.ts @@ -1,4 +1,5 @@ import {Except} from './except'; +import {Simplify} from './simplify'; /** Create a type that makes the given keys required. The remaining keys are kept as is. The sister of the `SetOptional` type. @@ -23,12 +24,10 @@ type SomeRequired = SetRequired; // } ``` */ -export type SetRequired = - // Pick just the keys that are not required from the base type. - Except & - // Pick the keys that should be required from the base type and make them required. - Required> extends - // If `InferredType` extends the previous, then for each key, use the inferred type key. - infer InferredType - ? {[KeyType in keyof InferredType]: InferredType[KeyType]} - : never; +export type SetRequired = + Simplify< + // Pick just the keys that are optional from the base type. + Except & + // Pick the keys that should be required from the base type and make them required. + Required> + >; diff --git a/source/simplify.d.ts b/source/simplify.d.ts new file mode 100644 index 000000000..5e067c24e --- /dev/null +++ b/source/simplify.d.ts @@ -0,0 +1,4 @@ +/** +Flatten the type output to improve type hints shown in editors. +*/ +export type Simplify = {[KeyType in keyof T]: T[KeyType]}; diff --git a/test-d/mutable.ts b/test-d/mutable.ts index f6ddf494b..a83374dcf 100644 --- a/test-d/mutable.ts +++ b/test-d/mutable.ts @@ -1,3 +1,4 @@ +import {expectType, expectError} from 'tsd'; import {Mutable} from '..'; type Foo = { @@ -9,3 +10,19 @@ const ab: Mutable = {a: 1, b: '2'}; ab.a = 2; const ab2: Mutable> = ab; ab2.a = 2; + +// Update one mutable and one readonly to mutable, leaving one property unaffected. +declare const variation1: Mutable<{readonly a: number; b: string; readonly c: boolean}, 'b' | 'c'>; +expectType<{readonly a: number; b: string; c: boolean}>(variation1); + +// Update two readonly to mutable, leaving one property unaffected. +declare const variation2: Mutable<{readonly a: number; readonly b: string; readonly c: boolean}, 'a' | 'b'>; +expectType<{a: number; b: string; readonly c: boolean}>(variation2); + +// Three mutable remain mutable. +declare const variation3: Mutable<{a: number; b: string; c: boolean}, 'a' | 'b' | 'c'>; +expectType<{a: number; b: string; c: boolean}>(variation3); + +// Check if type changes raise an error even if readonly and mutable are applied correctly. +declare const variation4: Mutable<{readonly a: number; b: string; readonly c: boolean}, 'b' | 'c'>; +expectError<{readonly a: boolean; b: string; c: boolean}>(variation4);