diff --git a/README.md b/README.md index 7ecc8b4..75c8a09 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ npm install --save-dev simplytyped **[Objects](#objects)** -[AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) +[AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [ElementwiseIntersect](#elementwiseintersect) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [TryKey](#trykey) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) **[Utils](#utils)** @@ -88,12 +88,12 @@ Useful for making extremely complex types look nice in VSCode. ```ts test('Can combine two objects (without pesky & in vscode)', t => { type a = { x: number, y: 'hi' }; - type b = { z: number, y: 'there' }; + type b = { z: number }; type got = CombineObjects; type expected = { x: number, - y: 'hi' & 'there', + y: 'hi', z: number }; @@ -231,6 +231,51 @@ test('Can get all keys that are different between objects', t => { }); ``` +### ElementwiseIntersect +Takes two objects and returns their element-wise intersection. +*Note*: this removes any key-level information, such as optional or readonly keys. +```ts +test('Can combine two objects elementwise', t => { + type a = { x: number, y: 'hi' }; + type b = { z: number, y: 'there' }; + + type got = ElementwiseIntersect; + type expected = { + x: number, + y: 'hi' & 'there', + z: number, + }; + + assert(t); + assert(t); +}); + +test('Can combine two objects with private members elementwise', t => { + class A { + a: number = 1; + private x: number = 2; + y: 'hi' = 'hi'; + private z: 'hey' = 'hey'; + } + + class B { + a: 22 = 22; + private x: number = 2; + y: 'there' = 'there'; + private z: 'friend' = 'friend'; + } + + type got = ElementwiseIntersect; + type expected = { + a: 22, + y: 'hi' & 'there', + }; + + assert(t); + assert(t); +}); +``` + ### GetKey Gets the value of specified property on any object without compile time error (`Property 'b' does not exist on type '{ a: string; }'.`) and the like. Returns `never` if the key is not on the object. @@ -471,6 +516,10 @@ For discriminated unions of objects, it is important to have a single "tag" prop Creates an object with each entry being tagged by the key defining that entry. +### TryKey +Like `GetKey`, but returns `unknown` if the key is not present on the object. + + ### UnionizeProperties Get a union of the properties of an object. ```ts diff --git a/scripts/testTsVersions.sh b/scripts/testTsVersions.sh index d6c1bac..319d0ce 100644 --- a/scripts/testTsVersions.sh +++ b/scripts/testTsVersions.sh @@ -1,6 +1,7 @@ set -e -for v in 3.0.3 3.1.6 3.2.2 next; do +# highest patch version of each minor version +for v in 3.0.3 3.1.6 3.2.4 3.3.4000 3.4.5 3.5.3 3.6.5 3.7.5 3.8.3 next; do npm install --no-save typescript@$v npm test done diff --git a/src/types/objects.ts b/src/types/objects.ts index 1c9f64d..36a0871 100644 --- a/src/types/objects.ts +++ b/src/types/objects.ts @@ -34,6 +34,23 @@ export type CombineObjects = ObjectType = K extends keyof T ? T[K] : never; +/** + * Like `GetKey`, but returns `unknown` if the key is not present on the object. + * @param T Object to get values from + * @param K Key to query object for value + * @returns `T[K]` if the key exists, `unknown` otherwise + */ +export type TryKey = K extends keyof T ? T[K]: unknown; +/** + * Takes two objects and returns their element-wise intersection. + * *Note*: this removes any key-level information, such as optional or readonly keys. + * @param T First object to be intersected + * @param U Second object to be intersected + * @returns element-wise `T` & `U` cleaned up to look like flat object to VSCode + */ +export type ElementwiseIntersect = { + [k in (keyof T | keyof U)]: TryKey & TryKey; +}; // ---- // Keys diff --git a/test/objects/CombineObjects.test.ts b/test/objects/CombineObjects.test.ts index 50dac54..f7da339 100644 --- a/test/objects/CombineObjects.test.ts +++ b/test/objects/CombineObjects.test.ts @@ -5,12 +5,12 @@ import { CombineObjects } from '../../src'; test('Can combine two objects (without pesky & in vscode)', t => { type a = { x: number, y: 'hi' }; - type b = { z: number, y: 'there' }; + type b = { z: number }; type got = CombineObjects; type expected = { x: number, - y: 'hi' & 'there', + y: 'hi', z: number }; diff --git a/test/objects/ElementwiseIntersect.test.ts b/test/objects/ElementwiseIntersect.test.ts new file mode 100644 index 0000000..937824e --- /dev/null +++ b/test/objects/ElementwiseIntersect.test.ts @@ -0,0 +1,44 @@ +import test from 'ava'; +import { assert } from '../helpers/assert'; + +import { ElementwiseIntersect } from '../../src'; + +test('Can combine two objects elementwise', t => { + type a = { x: number, y: 'hi' }; + type b = { z: number, y: 'there' }; + + type got = ElementwiseIntersect; + type expected = { + x: number, + y: 'hi' & 'there', + z: number, + }; + + assert(t); + assert(t); +}); + +test('Can combine two objects with private members elementwise', t => { + class A { + a: number = 1; + private x: number = 2; + y: 'hi' = 'hi'; + private z: 'hey' = 'hey'; + } + + class B { + a: 22 = 22; + private x: number = 2; + y: 'there' = 'there'; + private z: 'friend' = 'friend'; + } + + type got = ElementwiseIntersect; + type expected = { + a: 22, + y: 'hi' & 'there', + }; + + assert(t); + assert(t); +});