Skip to content

Commit

Permalink
Add Mutable type (#157)
Browse files Browse the repository at this point in the history
Co-authored-by: Pelle Wessman <pelle@kodfabrik.se>
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
3 people committed Dec 31, 2020
1 parent ce5b200 commit 673c1aa
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 27 deletions.
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -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<T>`.
- [`Mutable`](source/mutable.d.ts) - Create a type that strips `readonly` from all or some of an object's keys. The inverse of `Readonly<T>`.
- [`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.
Expand Down
32 changes: 24 additions & 8 deletions source/mutable.d.ts
@@ -1,22 +1,38 @@
import {Except} from './except';
import {Simplify} from './simplify';

/**
Convert an object with `readonly` keys into a mutable object. Inverse of `Readonly<T>`.
Create a type that strips `readonly` from all or some of an object's keys. Inverse of `Readonly<T>`.
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
```
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<Foo> = {a: 1, b: '2'};
const mutableFoo: Mutable<Foo> = {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<Foo, 'b' | 'c'>;
// 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<ObjectType> = {
// 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<BaseType, Keys extends keyof BaseType = keyof BaseType> =
Simplify<
// Pick just the keys that are not mutable from the base type.
Except<BaseType, Keys> &
// 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<BaseType, Keys>]: Pick<BaseType, Keys>[KeyType]}
>;
17 changes: 8 additions & 9 deletions 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.
Expand All @@ -23,12 +24,10 @@ type SomeOptional = SetOptional<Foo, 'b' | 'c'>;
// }
```
*/
export type SetOptional<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not optional from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be optional from the base type and make them optional.
Partial<Pick<BaseType, Keys>> 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<BaseType, Keys extends keyof BaseType> =
Simplify<
// Pick just the keys that are readonly from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be mutable from the base type and make them mutable.
Partial<Pick<BaseType, Keys>>
>;
17 changes: 8 additions & 9 deletions 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.
Expand All @@ -23,12 +24,10 @@ type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// }
```
*/
export type SetRequired<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not required from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be required from the base type and make them required.
Required<Pick<BaseType, Keys>> 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<BaseType, Keys extends keyof BaseType> =
Simplify<
// Pick just the keys that are optional from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be required from the base type and make them required.
Required<Pick<BaseType, Keys>>
>;
4 changes: 4 additions & 0 deletions source/simplify.d.ts
@@ -0,0 +1,4 @@
/**
Flatten the type output to improve type hints shown in editors.
*/
export type Simplify<T> = {[KeyType in keyof T]: T[KeyType]};
17 changes: 17 additions & 0 deletions test-d/mutable.ts
@@ -1,3 +1,4 @@
import {expectType, expectError} from 'tsd';
import {Mutable} from '..';

type Foo = {
Expand All @@ -9,3 +10,19 @@ const ab: Mutable<Foo> = {a: 1, b: '2'};
ab.a = 2;
const ab2: Mutable<Readonly<Foo>> = 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);

0 comments on commit 673c1aa

Please sign in to comment.