Skip to content

Commit

Permalink
Add PartialDeep type (#47)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
Co-authored-by: Dimitri Benin <BendingBender@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 16, 2019
1 parent b29c31a commit 80465bc
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -7,6 +7,7 @@ export {Mutable} from './source/mutable';
export {Merge} from './source/merge';
export {MergeExclusive} from './source/merge-exclusive';
export {RequireAtLeastOne} from './source/require-at-least-one';
export {PartialDeep} from './source/partial-deep';
export {ReadonlyDeep} from './source/readonly-deep';
export {LiteralUnion} from './source/literal-union';
export {Promisable} from './source/promisable';
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Expand Up @@ -69,7 +69,8 @@ Click the type names for complete docs.
- [`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 properties.
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given properties.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of a `object`/`Map`/`Set`/`Array` type.
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1401-L1406) if you only need one level deep.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://github.com/Microsoft/TypeScript/blob/2961bc3fc0ea1117d4e53bc8e97fa76119bc33e3/src/lib/es5.d.ts#L1415-L1420) if you only need one level deep.
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
- [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`.
- [`Opaque`](source/opaque.d.ts) - Create an [opaque type](https://codemix.com/opaque-types-in-javascript/).
Expand Down
72 changes: 72 additions & 0 deletions source/partial-deep.d.ts
@@ -0,0 +1,72 @@
import {Primitive} from './basic';

/**
Create a type from another type with all keys and nested keys set to optional.
Use-cases:
- Merging a default settings/config object with another object, the second object would be a deep partial of the default object.
- Mocking and testing complex entities, where populating an entire object with its properties would be redundant in terms of the mock or test.
@example
```
import {PartialDeep} from 'type-fest';
const settings: Settings = {
textEditor: {
fontSize: 14;
fontColor: '#000000';
fontWeight: 400;
}
autocomplete: false;
autosave: true;
};
const applySavedSettings = (savedSettings: PartialDeep<Settings>) => {
return {...settings, ...savedSettings};
}
settings = applySavedSettings({textEditor: {fontWeight: 500}});
```
*/
export type PartialDeep<T> = T extends Primitive
? Partial<T>
: T extends Map<infer KeyType, infer ValueType>
? PartialMapDeep<KeyType, ValueType>
: T extends Set<infer ItemType>
? PartialSetDeep<ItemType>
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? PartialReadonlyMapDeep<KeyType, ValueType>
: T extends ReadonlySet<infer ItemType>
? PartialReadonlySetDeep<ItemType>
: T extends ((...arguments: any[]) => unknown)
? T | undefined
: T extends object
? PartialObjectDeep<T>
: unknown;

/**
Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`.
*/
interface PartialMapDeep<KeyType, ValueType> extends Map<PartialDeep<KeyType>, PartialDeep<ValueType>> {}

/**
Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialSetDeep<T> extends Set<PartialDeep<T>> {}

/**
Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialReadonlyMapDeep<KeyType, ValueType> extends ReadonlyMap<PartialDeep<KeyType>, PartialDeep<ValueType>> {}

/**
Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`.
*/
interface PartialReadonlySetDeep<T> extends ReadonlySet<PartialDeep<T>> {}

/**
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialObjectDeep<ObjectType extends object> = {
[PropertyType in keyof ObjectType]: PartialDeep<ObjectType[PropertyType]> | undefined
};
46 changes: 46 additions & 0 deletions test-d/partial-deep.ts
@@ -0,0 +1,46 @@
import {expectType, expectError} from 'tsd';
import {PartialDeep} from '..';

const foo = {
baz: 'fred',
bar: {
function: (_: string): void => {},
object: {key: 'value'},
string: 'waldo',
number: 1,
boolean: false,
symbol: Symbol('test'),
null: null,
undefined: undefined, // eslint-disable-line object-shorthand
map: new Map<string, string>(),
set: new Set<string>(),
array: ['foo'],
tuple: ['foo'] as ['foo'],
readonlyMap: new Map<string, string>() as ReadonlyMap<string, string>,
readonlySet: new Set<string>() as ReadonlySet<string>,
readonlyArray: ['foo'] as readonly string[],
readonlyTuple: ['foo'] as const
}
};

const partialDeepFoo: PartialDeep<typeof foo> = foo;

expectError(expectType<Partial<typeof foo>>(partialDeepFoo));
const partialDeepBar: PartialDeep<typeof foo.bar> = foo.bar;
expectType<typeof partialDeepBar | undefined>(partialDeepFoo.bar);
expectType<((_: string) => void) | undefined>(partialDeepFoo.bar!.function);
expectType<object | undefined>(partialDeepFoo.bar!.object);
expectType<string | undefined>(partialDeepFoo.bar!.string);
expectType<number | undefined>(partialDeepFoo.bar!.number);
expectType<boolean | undefined>(partialDeepFoo.bar!.boolean);
expectType<symbol | undefined>(partialDeepFoo.bar!.symbol);
expectType<null | undefined>(partialDeepFoo.bar!.null);
expectType<undefined>(partialDeepFoo.bar!.undefined);
expectType<Map<string | undefined, string | undefined> | undefined>(partialDeepFoo.bar!.map);
expectType<Set<string | undefined> | undefined>(partialDeepFoo.bar!.set);
expectType<Array<string | undefined> | undefined>(partialDeepFoo.bar!.array);
expectType<['foo' | undefined] | undefined>(partialDeepFoo.bar!.tuple);
expectType<ReadonlyMap<string | undefined, string | undefined> | undefined>(partialDeepFoo.bar!.readonlyMap);
expectType<ReadonlySet<string | undefined> | undefined>(partialDeepFoo.bar!.readonlySet);
expectType<ReadonlyArray<string | undefined> | undefined>(partialDeepFoo.bar!.readonlyArray);
expectType<readonly ['foo' | undefined] | undefined>(partialDeepFoo.bar!.readonlyTuple);

0 comments on commit 80465bc

Please sign in to comment.