Skip to content

feat(types): allow setting unions in stores #1058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 75 additions & 65 deletions packages/solid/store/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,107 +297,117 @@ export type StoreSetter<T, U extends PropertyKey[] = []> =
export type Part<T, K extends KeyOf<T> = KeyOf<T>> =
| K
| ([K] extends [never] ? never : readonly K[])
| ([T] extends [readonly unknown[]] ? ArrayFilterFn<T[number]> | StorePathRange : never);
| (T extends readonly unknown[] ? ArrayFilterFn<T[number]> | StorePathRange : never);

// shortcut to avoid writing `Exclude<T, NotWrappable>` too many times
type W<T> = Exclude<T, NotWrappable>;

// access only instances of T where K is keyof T, A for Access
type A<T, K extends PropertyKey> = T extends unknown ? T[K & keyof T] : never;

// specially handle keyof to avoid errors with arrays and any
type KeyOf<T> = number extends keyof T // have to check this otherwise ts won't allow KeyOf<T> to index T
? 0 extends 1 & T // if it's any just return keyof T
? keyof T
: [T] extends [readonly unknown[]]
? number // it's an array or tuple; exclude the non-number properties
: [T] extends [never]
? never // keyof never is PropertyKey which number extends; return never
: keyof T // it's something which contains an index signature for strings or numbers
: keyof T;
type KeyOf<T> = T extends unknown // distribute T in case it is a union
? keyof T &
(0 extends 1 & T
? unknown // don't narrow further than keyof T if it is any
: [T] extends [never]
? never // keyof never is PropertyKey instead of never: return never
: [T] extends [readonly unknown[]]
? number | `${number}` // extract numbers if T is an array
: unknown) // don't narrow further if T is something else
: never;

type Rest<T, U extends PropertyKey[]> =
| [StoreSetter<T, U>]
| (0 extends 1 & T
? [...Part<any>[], StoreSetter<any, PropertyKey[]>]
: DistributeRest<W<T>, KeyOf<W<T>>, U>);
// need a second type to distribute `K`
type DistributeRest<T, K, U extends PropertyKey[]> = [T] extends [never]
? never
: K extends KeyOf<T>
? [Part<T, K>, ...Rest<T[K], [K, ...U]>]
: never;
: T extends NotWrappable
? never
: { [K in KeyOf<T>]: [Part<T, K>, ...Rest<T[K], [K, ...U]>] }[KeyOf<T>]);

export interface SetStoreFunction<T> {
<
K1 extends KeyOf<W<T>>,
K2 extends KeyOf<W<W<T>[K1]>>,
K3 extends KeyOf<W<W<W<T>[K1]>[K2]>>,
K4 extends KeyOf<W<W<W<W<T>[K1]>[K2]>[K3]>>,
K5 extends KeyOf<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>>,
K6 extends KeyOf<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>>,
K7 extends KeyOf<W<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>>
K2 extends KeyOf<W<A<W<T>, K1>>>,
K3 extends KeyOf<W<A<W<A<W<T>, K1>>, K2>>>,
K4 extends KeyOf<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>>,
K5 extends KeyOf<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>>,
K6 extends KeyOf<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>>,
K7 extends KeyOf<W<A<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>>>
>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
k3: Part<W<W<W<T>[K1]>[K2]>, K3>,
k4: Part<W<W<W<W<T>[K1]>[K2]>[K3]>, K4>,
k5: Part<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>, K5>,
k6: Part<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>, K6>,
k7: Part<W<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>, K7>,
...rest: Rest<W<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>[K7], [K7, K6, K5, K4, K3, K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
k3: Part<W<A<W<A<W<T>, K1>>, K2>>, K3>,
k4: Part<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>,
k5: Part<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>,
k6: Part<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>,
k7: Part<W<A<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>>, K7>,
...rest: Rest<
A<W<A<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>>, K7>,
[K7, K6, K5, K4, K3, K2, K1]
>
): void;
<
K1 extends KeyOf<W<T>>,
K2 extends KeyOf<W<W<T>[K1]>>,
K3 extends KeyOf<W<W<W<T>[K1]>[K2]>>,
K4 extends KeyOf<W<W<W<W<T>[K1]>[K2]>[K3]>>,
K5 extends KeyOf<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>>,
K6 extends KeyOf<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>>
K2 extends KeyOf<W<A<W<T>, K1>>>,
K3 extends KeyOf<W<A<W<A<W<T>, K1>>, K2>>>,
K4 extends KeyOf<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>>,
K5 extends KeyOf<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>>,
K6 extends KeyOf<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>>
>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
k3: Part<W<W<W<T>[K1]>[K2]>, K3>,
k4: Part<W<W<W<W<T>[K1]>[K2]>[K3]>, K4>,
k5: Part<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>, K5>,
k6: Part<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>, K6>,
setter: StoreSetter<W<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5]>[K6], [K6, K5, K4, K3, K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
k3: Part<W<A<W<A<W<T>, K1>>, K2>>, K3>,
k4: Part<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>,
k5: Part<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>,
k6: Part<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>,
setter: StoreSetter<
A<W<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>>, K6>,
[K6, K5, K4, K3, K2, K1]
>
): void;
<
K1 extends KeyOf<W<T>>,
K2 extends KeyOf<W<W<T>[K1]>>,
K3 extends KeyOf<W<W<W<T>[K1]>[K2]>>,
K4 extends KeyOf<W<W<W<W<T>[K1]>[K2]>[K3]>>,
K5 extends KeyOf<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>>
K2 extends KeyOf<W<A<W<T>, K1>>>,
K3 extends KeyOf<W<A<W<A<W<T>, K1>>, K2>>>,
K4 extends KeyOf<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>>,
K5 extends KeyOf<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>>
>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
k3: Part<W<W<W<T>[K1]>[K2]>, K3>,
k4: Part<W<W<W<W<T>[K1]>[K2]>[K3]>, K4>,
k5: Part<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>, K5>,
setter: StoreSetter<W<W<W<W<W<T>[K1]>[K2]>[K3]>[K4]>[K5], [K5, K4, K3, K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
k3: Part<W<A<W<A<W<T>, K1>>, K2>>, K3>,
k4: Part<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>,
k5: Part<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>,
setter: StoreSetter<A<W<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>>, K5>, [K5, K4, K3, K2, K1]>
): void;
<
K1 extends KeyOf<W<T>>,
K2 extends KeyOf<W<W<T>[K1]>>,
K3 extends KeyOf<W<W<W<T>[K1]>[K2]>>,
K4 extends KeyOf<W<W<W<W<T>[K1]>[K2]>[K3]>>
K2 extends KeyOf<W<A<W<T>, K1>>>,
K3 extends KeyOf<W<A<W<A<W<T>, K1>>, K2>>>,
K4 extends KeyOf<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>>
>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
k3: Part<W<W<W<T>[K1]>[K2]>, K3>,
k4: Part<W<W<W<W<T>[K1]>[K2]>[K3]>, K4>,
setter: StoreSetter<W<W<W<W<T>[K1]>[K2]>[K3]>[K4], [K4, K3, K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
k3: Part<W<A<W<A<W<T>, K1>>, K2>>, K3>,
k4: Part<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>,
setter: StoreSetter<A<W<A<W<A<W<A<W<T>, K1>>, K2>>, K3>>, K4>, [K4, K3, K2, K1]>
): void;
<K1 extends KeyOf<W<T>>, K2 extends KeyOf<W<W<T>[K1]>>, K3 extends KeyOf<W<W<W<T>[K1]>[K2]>>>(
<
K1 extends KeyOf<W<T>>,
K2 extends KeyOf<W<A<W<T>, K1>>>,
K3 extends KeyOf<W<A<W<A<W<T>, K1>>, K2>>>
>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
k3: Part<W<W<W<T>[K1]>[K2]>, K3>,
setter: StoreSetter<W<W<W<T>[K1]>[K2]>[K3], [K3, K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
k3: Part<W<A<W<A<W<T>, K1>>, K2>>, K3>,
setter: StoreSetter<A<W<A<W<A<W<T>, K1>>, K2>>, K3>, [K3, K2, K1]>
): void;
<K1 extends KeyOf<W<T>>, K2 extends KeyOf<W<W<T>[K1]>>>(
<K1 extends KeyOf<W<T>>, K2 extends KeyOf<W<A<W<T>, K1>>>>(
k1: Part<W<T>, K1>,
k2: Part<W<W<T>[K1]>, K2>,
setter: StoreSetter<W<W<T>[K1]>[K2], [K2, K1]>
k2: Part<W<A<W<T>, K1>>, K2>,
setter: StoreSetter<A<W<A<W<T>, K1>>, K2>, [K2, K1]>
): void;
<K1 extends KeyOf<W<T>>>(k1: Part<W<T>, K1>, setter: StoreSetter<W<T>[K1], [K1]>): void;
<K1 extends KeyOf<W<T>>>(k1: Part<W<T>, K1>, setter: StoreSetter<A<W<T>, K1>, [K1]>): void;
(setter: StoreSetter<T, []>): void;
}

Expand Down
25 changes: 24 additions & 1 deletion packages/solid/store/test/store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ describe("Nested Classes", () => {

// type tests

// NotWrappable keys are ignored
// setter ignores NotWrappable keys (unsafe)
() => {
const [, setStore] = createStore<{
a?:
Expand All @@ -726,6 +726,29 @@ describe("Nested Classes", () => {
setStore("a", "b", "c", "d", "e", "f", "g", "h");
};

// setter can set type unions as if it were a member (unsafe)
() => {
interface Game {
name: string;
}
interface Boardgame extends Game {
type: "boardgame";
nested: { pieces: number };
}
interface VideoGame extends Game {
type: "videogame";
otherNested: { platform: "Sony" | "Mircosoft" | "Nintendo" };
}
type GameUnion = Boardgame | VideoGame;
const [games, setGames] = createStore<GameUnion[]>([
{ name: "Settlers", nested: { pieces: 240 } } as Boardgame,
{ name: "Zelda", otherNested: { platform: "Nintendo" } } as VideoGame
]);

games[0].type === "boardgame" && games[0].nested.pieces;
setGames(0, "nested", "pieces", Math.floor(Math.random() * 100));
};

// keys are narrowed
() => {
const [store, setStore] = createStore({ a: { b: 1 }, c: { d: 2 } });
Expand Down