Created
January 15, 2019 18:33
-
-
Save alxhub/577cec0bde4c4600e6e0cab2e380f8aa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Setup: RxJS shaped types | |
declare class Observable<T> { _t: T; } | |
declare interface ObservedValueOf<T> { _t: T; _brand: 'ObservedValueOf'; } | |
declare interface Scheduler { _brand: 'Scheduler'; } | |
declare type ObservableInput<T> = Observable<T> | Promise<T> | T[]; | |
// A trick to force T to be inferred as a tuple type. TypeScript would prefer to infer | |
// an array, but arrays don't match {0: any}. | |
type Tuple<T> = (T & { 0: any }) | []; | |
// Map ObservableInput<T> to T. | |
declare type ObservableInputToObservedValueOf<T> = T extends ObservableInput<infer V> ? ObservedValueOf<V> : never; | |
// Map a Tuple of ObservableInputs to a Tuple of ObservedValueOf of their respective types. | |
// Ex: [Observable<string>, number[]] -> [ObservedValueOf<string>, ObservedValueOf<number>] | |
declare type ObservableInputTupleToObservedValueOfTuple<T extends any[]> = { | |
[I in keyof T]: ObservableInputToObservedValueOf<T[I]>; | |
}; | |
// A signature for combineLatest which accepts any number of elements in an array | |
// as the first argument and returns a correctly typed result. | |
declare function combineLatest<T extends ObservableInput<unknown>[]>(values: Tuple<T>, scheduler?: Scheduler): Observable<ObservableInputTupleToObservedValueOfTuple<T>>; | |
declare const obsString: Observable<string>; | |
declare const obsNumber: Observable<number>; | |
declare const obsBool: Observable<boolean>; | |
declare const scheduler: Scheduler; | |
// Type should be Observable<[ObservedValueOf<string>, ObservedValueOf<number>, ObservedValueOf<boolean>]> | |
const res = combineLatest([obsString, obsNumber, obsBool]); | |
// Tuple operations borrowed from https://github.com/Microsoft/TypeScript/issues/25947. | |
type Head<T> = T extends [infer U, ...unknown[]] ? U : never; | |
type Tail<T> = T extends Array<any> | |
? ((...args: T) => never) extends ((a: any, ...args: infer R) => never) | |
? R | |
: never | |
: never; | |
type Cons<T extends any[], H> = ((h: H, ...t: T) => any) extends ((...x: infer X) => any) ? X : never; | |
interface Reduction<Base, In> { | |
0: Base | |
1: In | |
} | |
type Reduce<T extends Array<any>, R extends Reduction<any, any>> = R[[T] extends [[]] ? 0 : 1]; | |
interface ReverseRec<H extends Array<any>, T extends Array<any>> | |
extends Reduction<H, Reduce<Tail<T>, ReverseRec<Cons<H, Head<T>>, Tail<T>>>> {} | |
type Reverse<T> = [T] extends [Array<any>] ? Reduce<T, ReverseRec<[], T>> : never; | |
// The goal is to correctly type combineLatest(o1: ObservableInput<O1>, o2: ObservableInput<O2>, ..., scheduler?: Scheduler): ... | |
// with any number of ObservableInput arguments, followed by the optional Scheduler. | |
// The only way to do this is to accept the whole argument list as a Tuple and extract | |
// parts of it as needed. The above Reverse operation is essential as the Scheduler | |
// is optionally at the end. | |
// One more utility type needed - ensure T is inferred as an array. | |
type AsArray<T extends any> = T extends any[] ? T : never; | |
// Get the last value of a tuple. | |
type Last<T extends any[]> = Head<Reverse<T>>; | |
// Get all but the last value of a tuple. AsArray is needed here because Reverse can return | |
// some strange synthetic types which never actually happen. | |
type AllButLast<T extends any[]> = AsArray<Reverse<Tail<Reverse<T>>>>; | |
// Represents a variable number of arguments, the majority of which satisfy RepeatT | |
// and the last one is optionally OptionalEndT. | |
type VarArgsWithOptionalEnd<RepeatT, OptionalEndT, ArrayT extends any[]> = Last<ArrayT> extends OptionalEndT | |
// The last argument is the optional one, so every other argument should be RepeatT. | |
? (AllButLast<ArrayT> extends RepeatT[] ? ArrayT : never) | |
// The last argument isn't the optional one, so every argument should be RepeatT. | |
: (ArrayT extends RepeatT[] ? ArrayT : never); | |
// Get ArrayT without the last argument of OptionalEndT if present. | |
type SkipOptionalEnd<ArrayT extends any[], OptionalEndT> = Last<ArrayT> extends OptionalEndT ? AllButLast<ArrayT> : ArrayT; | |
// Specifically typed arguments for combineLatest(). This needs to be separate because | |
// ArrayT needs to be inferred, but it can't be if the other two are passed in directly. | |
type CombineLatestVarArgs<ArrayT extends any[]> = ArrayT extends VarArgsWithOptionalEnd<ObservableInput<unknown>, Scheduler, ArrayT> ? ArrayT : never; | |
// Finally, we can write combineLatest2(). | |
declare function combineLatest2<A extends any[]>(...args: CombineLatestVarArgs<A>): Observable<ObservableInputTupleToObservedValueOfTuple<SkipOptionalEnd<A, Scheduler>>>; | |
// Type should be Observable<[ObservedValueOf<string>, ObservedValueOf<number>, ObservedValueOf<boolean>]> | |
const res2 = combineLatest2(obsString, obsNumber, obsBool, scheduler); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment