Skip to content

Instantly share code, notes, and snippets.

@tonyg
Created February 17, 2021 11:25
Show Gist options
  • Save tonyg/a1f57d5d52f4363ddf03d7a06c69788f to your computer and use it in GitHub Desktop.
Save tonyg/a1f57d5d52f4363ddf03d7a06c69788f to your computer and use it in GitHub Desktop.
// This Tuple type (and tuple() function) is a hack to induce
// TypeScript to infer tuple types rather than array types. (Source:
// https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422606990)
//
// Without it, [123, 'hi', true] will often get the type (string |
// number | boolean)[] instead of [number, string, boolean].
//
export type Tuple = any[] | [any];
export const tuple = <A extends Tuple>(... args: A) => args;
// Type ValidSelector captures TypeScript's notion of a valid object
// property name.
//
export type ValidSelector = string | number | symbol;
export type EventMessage<Selector extends ValidSelector, Args extends any[]> =
{ selector: Selector, args: Args };
export type RequestMessage<Selector extends ValidSelector, Args extends any[], Result extends Exclude<any, void>> =
{ selector: Selector, args: Args, callback: (result: Result) => void };
export type Message<Selector extends ValidSelector, Args extends any[], Result> =
void extends Result ? EventMessage<Selector, Args> : RequestMessage<Selector, Args, Result>;
// Function message() is needed for similar reasons to tuple() above:
// to help TypeScript infer the correct literal type for the selector
// (as well as the arguments).
//
export const message = <S extends ValidSelector, A extends Tuple, R>(m: Message<S, A, R>) => m;
type MessagesProduct<I, ContextArgs extends any[]> = {
[K in keyof I]: (I[K] extends (...args: [...ContextArgs, ...infer P]) => infer Q
? Message<K, P, Q>
: never);
};
export type Messages<I, ContextArgs extends any[] = []> = MessagesProduct<I, ContextArgs>[keyof I];
export type Methods<M extends { selector: ValidSelector }, ContextArgs extends any[] = []> = {
[S in M['selector']]: (
M extends RequestMessage<S, infer P, infer R>
? (void extends R ? never : (...args: [...ContextArgs, ...P]) => R)
: (M extends EventMessage<S, infer P>
? (...args: [...ContextArgs, ...P]) => void
: never));
};
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends RequestMessage<S, Tuple, any>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): (M extends RequestMessage<S, Tuple, infer R> ? R : never);
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends EventMessage<S, Tuple>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): void;
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends RequestMessage<S, Tuple, any>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): any
{
const r = i[m.selector](...ctxt, ... m.args);
m.callback?.(r);
return r;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment