Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Entry and Entries types #130

Merged
merged 12 commits into from Sep 30, 2020
2 changes: 2 additions & 0 deletions index.d.ts
Expand Up @@ -25,6 +25,8 @@ export {UnionToIntersection} from './source/union-to-intersection';
export {Stringified} from './source/stringified';
export {FixedLengthArray} from './source/fixed-length-array';
export {IterableElement} from './source/iterable-element';
export {Entry} from './source/entry';
export {Entries} from './source/entries';

// Miscellaneous
export {PackageJson} from './source/package-json';
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -84,6 +84,8 @@ Click the type names for complete docs.
- [`Stringified`](source/stringified.d.ts) - Create a type with the keys of the given type changed to `string` type.
- [`FixedLengthArray`](source/fixed-length-array.d.ts) - Create a type that represents an array of the given type and length.
- [`IterableElement`](source/iterable-element.d.ts) - Get the element type of an `Iterable`/`AsyncIterable`. For example, an array or a generator.
- [`Entry`](source/entry.d.ts) - Create a type that represents the type of an entry of a collection.
- [`Entries`](source/entries.d.ts) - Create a type that represents the type of the entries of a collection.

### Miscellaneous

Expand Down
57 changes: 57 additions & 0 deletions source/entries.d.ts
@@ -0,0 +1,57 @@
import {ArrayEntry, MapEntry, ObjectEntry, SetEntry} from './entry';

type ArrayEntries<BaseType extends readonly unknown[]> = Array<ArrayEntry<BaseType>>;
type MapEntries<BaseType> = Array<MapEntry<BaseType>>;
type ObjectEntries<BaseType> = Array<ObjectEntry<BaseType>>;
type SetEntries<BaseType extends Set<unknown>> = Array<SetEntry<BaseType>>;

/**
Many collections have an `entries` method which returns an array of a given object's own enumerable string-keyed property [key, value] pairs. The `Entries` type will return the type of that collection's entries.

For example the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries|`Object`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries|`Map`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries|`Array`}, and {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries|`Set`} collections all have this method. Note that `WeakMap` and `WeakSet` do not have this method since their entries are not enumerable.

@see `Entry` if you want to just access the type of a single entry.

@example
```
import {Entries} from 'type-fest';

interface Example {
someKey: number;
}

const manipulatesEntries = (examples: Entries<Example>) => examples.map(example => [
// Does some arbitrary processing on the key (with type information available)
example[0].toUpperCase(),

// Does some arbitrary processing on the value (with type information available)
example[1].toFixed()
]);

const example: Example = {someKey: 1};
const entries = Object.entries(example) as Entries<Example>;
const output = manipulatesEntries(entries);

// Objects
const objectExample = {a: 1};
const objectEntries: Entries<typeof objectExample> = [['a', 1]];

// Arrays
const arrayExample = ['a', 1];
const arrayEntries: Entries<typeof arrayExample> = [[0, 'a'], [1, 1]];

// Maps
const mapExample = new Map([['a', 1]]);
const mapEntries: Entries<typeof map> = [['a', 1]];

// Sets
const setExample = new Set(['a', 1]);
const setEntries: Entries<typeof setExample> = [['a', 'a'], [1, 1]];
```
*/
export type Entries<BaseType> =
BaseType extends Map<unknown, unknown> ? MapEntries<BaseType>
: BaseType extends Set<unknown> ? SetEntries<BaseType>
: BaseType extends unknown[] ? ArrayEntries<BaseType>
: BaseType extends object ? ObjectEntries<BaseType>
: never;
60 changes: 60 additions & 0 deletions source/entry.d.ts
@@ -0,0 +1,60 @@
type MapKey<BaseType> = BaseType extends Map<infer KeyType, unknown> ? KeyType : never;
type MapValue<BaseType> = BaseType extends Map<unknown, infer ValueType> ? ValueType : never;

type ArrayEntry<BaseType extends readonly unknown[]> = [number, BaseType[number]];
type MapEntry<BaseType> = [MapKey<BaseType>, MapValue<BaseType>];
type ObjectEntry<BaseType> = [keyof BaseType, BaseType[keyof BaseType]];
type SetEntry<BaseType> = BaseType extends Set<infer ItemType> ? [ItemType, ItemType] : never;

/**
Many collections have an `entries` method which returns an array of a given object's own enumerable string-keyed property [key, value] pairs. The `Entry` type will return the type of that collection's entry.

For example the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries|`Object`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries|`Map`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries|`Array`}, and {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries|`Set`} collections all have this method. Note that `WeakMap` and `WeakSet` do not have this method since their entries are not enumerable.

@see `Entries` if you want to just access the type of the array of entries (which is the return of the `.entries()` method).

@example
```
import {Entry} from 'type-fest';

interface Example {
someKey: number;
}

const manipulatesEntry = (example: Entry<Example>) => [
// Does some arbitrary processing on the key (with type information available)
example[0].toUpperCase(),

// Does some arbitrary processing on the value (with type information available)
example[1].toFixed(),
];

const example: Example = {someKey: 1};
const entry = Object.entries(example)[0] as Entry<Example>;
const output = manipulatesEntry(entry);

// Objects
const objectExample = {a: 1};
const objectEntry: Entry<typeof objectExample> = ['a', 1];

// Arrays
const arrayExample = ['a', 1];
const arrayEntryString: Entry<typeof arrayExample> = [0, 'a'];
const arrayEntryNumber: Entry<typeof arrayExample> = [1, 1];

// Maps
const mapExample = new Map([['a', 1]]);
const mapEntry: Entry<typeof map> = ['a', 1];

// Sets
const setExample = new Set(['a', 1]);
const setEntryString: Entry<typeof setExample> = ['a', 'a'];
const setEntryNumber: Entry<typeof setExample> = [1, 1];
```
*/
export type Entry<BaseType> =
BaseType extends Map<unknown, unknown> ? MapEntry<BaseType>
: BaseType extends Set<unknown> ? SetEntry<BaseType>
: BaseType extends unknown[] ? ArrayEntry<BaseType>
: BaseType extends object ? ObjectEntry<BaseType>
: never;
51 changes: 51 additions & 0 deletions test-d/entries.ts
@@ -0,0 +1,51 @@
import {Entries} from '../source/entries';
import {Entry} from '../source/entry';
import {expectAssignable} from 'tsd';

// Objects
const objectExample = {a: 1};

const objectEntry: Entry<typeof objectExample> = ['a', 1];
expectAssignable<[string, number]>(objectEntry);

const objectEntries: Entries<typeof objectExample> = [objectEntry];
expectAssignable<Array<[string, number]>>(objectEntries);

// Maps
const mapExample = new Map([['a', 1]]);

const mapEntry: Entry<typeof mapExample> = ['a', 1];
expectAssignable<[string, number]>(mapEntry);

const mapEntries: Entries<typeof mapExample> = [mapEntry];
expectAssignable<Array<[string, number]>>(mapEntries);

// Arrays
const arrayExample = ['a', 1];

const arrayEntryString: Entry<typeof arrayExample> = [0, 'a'];
expectAssignable<[number, (string | number)]>(arrayEntryString);

const arrayEntryNumber: Entry<typeof arrayExample> = [1, 1];
expectAssignable<[number, (string | number)]>(arrayEntryNumber);

const arrayEntries: Entries<typeof arrayExample> = [
arrayEntryString,
arrayEntryNumber
];
expectAssignable<Array<[number, (string | number)]>>(arrayEntries);

// Sets
const setExample = new Set(['a', 1]);

const setEntryString: Entry<typeof setExample> = ['a', 'a'];
expectAssignable<[(string | number), (string | number)]>(setEntryString);

const setEntryNumber: Entry<typeof setExample> = [1, 1];
expectAssignable<[(string | number), (string | number)]>(setEntryNumber);

const setEntries: Entries<typeof setExample> = [
setEntryString,
setEntryNumber
];
expectAssignable<Array<[(string | number), (string | number)]>>(setEntries);