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 Merge<A, B> =
{ [K in keyof A]: K extends keyof B ? B[K] : A[K] } & B extends infer O ? { [K in Exclude<keyof O, keyof A>]: O[K] } : never;
type ValueOf<T> = T[keyof T];
type StateOf<T> = T extends StateSlice<any, infer S> ? S : never;
class StateSlice<T, K extends { [key: string]: T }> {
constructor(public state: K) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mergeStates<T extends StateSlice<any, any>>(
...slices: T[]
): StateSlice<ValueOf<Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>>, Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>> {
const newState = Object.assign(
{},
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return
...slices.map((slice) => slice.state)
) as Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>;
return new StateSlice(newState);
}
trim(keys: (keyof K)[]): StateSlice<T, Pick<K, (typeof keys)[number]>> {
const newState = keys.reduce(
(state, key) => ({ ...state, [key]: this.state[key] }),
{} as Pick<K, (typeof keys)[number]>
);
return new StateSlice(newState);
}
append<J extends string, V>(
key: J,
value: V
): StateSlice<T | V, K & { [P in J]: V }> {
return new StateSlice({ ...this.state, [key]: value });
}
split(
keys: (keyof K)[]
): [
StateSlice<T, Pick<K, (typeof keys)[number]>>,
StateSlice<T, Omit<K, (typeof keys)[number]>>
] {
const includedState = this.trim(keys).state;
const excludedState = Object.keys(this.state)
.filter((key) => !keys.includes(key as keyof K))
.reduce(
(state, key) => ({ ...state, [key]: this.state[key as keyof K] }),
{} as Omit<K, (typeof keys)[number]>
);
return [new StateSlice(includedState), new StateSlice(excludedState)];
}
}
function createStateSlice<T, K extends { [key: string]: T }>(
initialState: K
): StateSlice<T, K> {
return new StateSlice(initialState);
}
const UserSignIn = createStateSlice({
username: "",
password: "",
});
const UserPreferences = createStateSlice({
displayName: "",
email: "",
});
const UserPreferences2 = createStateSlice({
firstName: "",
lastName: "",
confirmPassword: "",
});
const UserSignUp = UserSignIn.mergeStates(UserPreferences, UserPreferences2);
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 Merge<A, B> =
{ [K in keyof A]: K extends keyof B ? B[K] : A[K] } & B extends infer O ? { [K in Exclude<keyof O, keyof A>]: O[K] } : never;
type ValueOf<T> = T[keyof T];
type StateOf<T> = T extends StateSlice<any, infer S> ? S : never;
class StateSlice<T, K extends { [key: string]: T }> {
constructor(public state: K) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mergeStates<T extends StateSlice<any, any>>(
...slices: T[]
): StateSlice<ValueOf<Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>>, Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>> {
const newState = Object.assign(
{},
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return
...slices.map((slice) => slice.state)
) as Merge<StateOf<LastOf<T>>, UnionToIntersection<StateOf<T>>>;
return new StateSlice(newState);
}
trim(keys: (keyof K)[]): StateSlice<T, Pick<K, (typeof keys)[number]>> {
const newState = keys.reduce(
(state, key) => ({ ...state, [key]: this.state[key] }),
{} as Pick<K, (typeof keys)[number]>
);
return new StateSlice(newState);
}
append<J extends string, V>(
key: J,
value: V
): StateSlice<T | V, K & { [P in J]: V }> {
return new StateSlice({ ...this.state, [key]: value });
}
split(
keys: (keyof K)[]
): [
StateSlice<T, Pick<K, (typeof keys)[number]>>,
StateSlice<T, Omit<K, (typeof keys)[number]>>
] {
const includedState = this.trim(keys).state;
const excludedState = Object.keys(this.state)
.filter((key) => !keys.includes(key as keyof K))
.reduce(
(state, key) => ({ ...state, [key]: this.state[key as keyof K] }),
{} as Omit<K, (typeof keys)[number]>
);
return [new StateSlice(includedState), new StateSlice(excludedState)];
}
}
function createStateSlice<T, K extends { [key: string]: T }>(
initialState: K
): StateSlice<T, K> {
return new StateSlice(initialState);
}
const UserSignIn = createStateSlice({
username: "",
password: "",
});
const UserPreferences = createStateSlice({
displayName: "",
email: "",
});
const UserPreferences2 = createStateSlice({
firstName: "",
lastName: "",
confirmPassword: "",
});
const UserSignUp = UserSignIn.mergeStates(UserPreferences, UserPreferences2);