Skip to content

Instantly share code, notes, and snippets.

@hansoksendahl
Created April 6, 2020 05:17
Show Gist options
  • Save hansoksendahl/971ad2f760b963ea8595badba6e9d0c2 to your computer and use it in GitHub Desktop.
Save hansoksendahl/971ad2f760b963ea8595badba6e9d0c2 to your computer and use it in GitHub Desktop.
/* eslint no-dupe-class-members: 0, lines-between-class-members: 0, class-methods-use-this: 0, require-jsdoc: 0 */
/**
* A signal accessor is a type safe getter and setter which corresponds to an interface.
* It can safely get and set nested properties up to an arbitrary depth of eight properties.
*
* @author Hans Oksendahl
*/
class SignalAccessor<T extends object> {
/**
* Create a new instance of Signal
*
* @returns Signal
*/
static of<U extends object>(): SignalAccessor<U> {
return new SignalAccessor<U>();
}
/**
* Create a nested property getter for a pre-defined type
*
* @param prop - {string|string[]} a property or array of nested properties
*/
public get<P extends keyof T>(prop: P): (ref: T) => T[P];
public get<P extends [keyof T]>(props: P): (ref: T) => T[P[0]];
public get<P extends [keyof T, keyof T[P[0]]]>(
props: P,
): (ref: T) => T[P[0]][P[1]];
public get<P extends [keyof T, keyof T[P[0]], keyof T[P[0]][P[1]]]>(
props: P,
): (ref: T) => T[P[0]][P[1]][P[2]];
public get<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
]
>(props: P): (ref: T) => T[P[0]][P[1]][P[2]][P[3]];
public get<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
]
>(props: P): (ref: T) => T[P[0]][P[1]][P[2]][P[3]][P[4]];
public get<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
]
>(props: P): (ref: T) => T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]];
public get<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]],
]
>(props: P): (ref: T) => T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]];
public get<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]],
]
>(props: P): (ref: T) => T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]][P[7]];
public get(segments: any): (ref: T) => any {
const props: string[] = [].concat(segments);
return (ref: T) => {
let currentRef: any = ref;
if (currentRef) {
/* eslint-disable-next-line no-restricted-syntax */
for (const prop of props) {
currentRef = currentRef[prop as keyof typeof currentRef];
if (!currentRef) {
break;
}
}
}
return currentRef;
};
}
/**
* Create a nested property setter for a pre-defined type
*
* @param prop - {string|string[]} a property or array of nested properties
* @param value - {*} a value with the same type as the named property
*/
set<P extends keyof T, U extends T[P]>(prop: P, value: U): (ref: T) => T;
set<P extends [keyof T], U extends T[P[0]]>(
props: P,
value: U,
): (ref: T) => T;
set<P extends [keyof T, keyof T[P[0]]], U extends T[P[0]][P[1]]>(
props: P,
value: U,
): (ref: T) => T;
set<
P extends [keyof T, keyof T[P[0]], keyof T[P[0]][P[1]]],
U extends T[P[0]][P[1]][P[2]]
>(props: P, value: U): (ref: T) => T;
set<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
],
U extends T[P[0]][P[1]][P[2]][P[3]]
>(props: P, value: U): (ref: T) => T;
set<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
],
U extends T[P[0]][P[1]][P[2]][P[3]][P[4]]
>(props: P, value: U): (ref: T) => T;
set<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
],
U extends T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]]
>(props: P, value: U): (ref: T) => T;
set<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]],
],
U extends T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]]
>(props: P, value: U): (ref: T) => T;
set<
P extends [
keyof T,
keyof T[P[0]],
keyof T[P[0]][P[1]],
keyof T[P[0]][P[1]][P[2]],
keyof T[P[0]][P[1]][P[2]][P[3]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]],
keyof T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]],
],
U extends T[P[0]][P[1]][P[2]][P[3]][P[4]][P[5]][P[6]][P[7]]
>(props: P, value: U): (ref: T) => T;
set(segments: any, value: any): (ref: T) => T {
const props = [].concat(segments);
const ancestors = props.slice(0, -1);
const last = props[props.length - 1];
return function (ref: T) {
let currentRef: any = ref;
if (currentRef) {
/* eslint-disable-next-line no-restricted-syntax */
for (const prop of ancestors) {
currentRef = currentRef[prop as keyof typeof currentRef];
if (!currentRef) {
break;
}
}
}
currentRef[last] = value;
return ref;
};
}
}
export default SignalAccessor.of;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment