events.track('SOME_EVENT', { first: 'a', other: 'b', some: 'c'})
export interface Events {
track: (name: string, params?: Record<string, string | number | unknown>) => void;
}
type EventParam = [name: string, value: string | number | unknown];
type EventParams =
[EventParam]
| [EventParam, EventParam]
| [EventParam, EventParam, EventParam];
export interface Events {
track: (name: string, params?: EventParams) => void;
}
declare let events: Events;
// Works with 1:
events.track("something", [["first", "a"]]);
// Works with 2:
events.track("something", [["first", "a"], ["other", "b"]]);
// Works with 3:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"]]);
// Fails with 4:
events.track("something", [["first", "a"], ["other", "b"], ["some", "c"], ["fourth", 42]]);
// Error as desired −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastOf<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never
type Push<T extends any[], V> = [...T, V];
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> =
true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
type MaxThreeProperties<T extends Record<string, any>> =
TuplifyUnion<keyof T>["length"] extends 0 | 1 | 2 | 3 ? T : never
// add all acceptable key amounts here ^ ^ ^ ^
function track<T extends Record<string, any>>(
name: string,
params: MaxThreeProperties<T>
) {}
track('SOME_EVENT', {}) // works
track('SOME_EVENT', {a: ""}) // works
track('SOME_EVENT', {a: "", b: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: ""}) // works
track('SOME_EVENT', {a: "", b: "", c: "", d: ""}) // ERROR
track('SOME_EVENT', {a: "", b: "", c: "", d: "", e: ""}) // ERROR
const a = {a: "", b: "", c: ""}
const b = {a: "", b: "", c: "", d: ""}
track('SOME_EVENT', a) // works
track('SOME_EVENT', b) // ERROR