Skip to content

Instantly share code, notes, and snippets.

@alxhub
Created January 15, 2019 18:33
Show Gist options
  • Save alxhub/577cec0bde4c4600e6e0cab2e380f8aa to your computer and use it in GitHub Desktop.
Save alxhub/577cec0bde4c4600e6e0cab2e380f8aa to your computer and use it in GitHub Desktop.
// 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