paroi-tech / direct-vuex

Use and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.
Creative Commons Zero v1.0 Universal
258 stars 14 forks source link

Typed mapGetters, mapActions and such #73

Open Papooch opened 3 years ago

Papooch commented 3 years ago

Are there any plans to add typed variants of these helper functions? I might give it a try myself but I don't know if something like that is possible with the current implementation. I guess you could pass the store as a parameter to extract the types?

something like:

computed: {
   ...mapDirectGetters(this.$store, ['username', 'email'])
}

I'd like to hear your thoughts on this.

EDIT: I mean, we can just return the mapDirectGetters from createDirectStore so we don't have to pass this.$store

so it would be:

// store/index.ts
export { store, mapDirectGetters } = createDirectStore( ... )

and then

computed: {
   ...mapDirectGetters(['username', 'email'])
}
Papooch commented 3 years ago

Ok, I have a proof of concept:

// pickable.ts
export type Primitive = string | number | boolean | bigint | null | void | symbol | Function;

export type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...0[]];

export type Leaves<T, U extends string = never, L extends number = 1> = [
    L,
] extends [never]
    ? U
    : {
          [K in keyof T & string]: [U] extends [never]
              ? T[K] extends Primitive
                  ? K
                  : Leaves<T[K], K, Prev[L]>
              : T[K] extends Primitive
              ? `${U}.${K}`
              : Leaves<T[K], `${U}.${K}`, Prev[L]>;
      }[keyof T & string];

export type ResolvePath<T, K> = K extends keyof T
    ? T[K]
    : K extends `${infer F}.${infer L}`
    ? ResolvePath<ResolvePath<T, F>, L>
    : never;

export type LastKey<T extends string> = T extends `${string}.${infer L}`
    ? LastKey<L & string>
    : T;

export function cherryPick<T, K extends readonly Leaves<T>[]>(
    obj: T,
    keys: K | [],
): { [K2 in K[number] as LastKey<K2>]: ResolvePath<T, K2> } {
    const ret = {} as any;
    keys.forEach((key) => {
        const path = key.split('.');
        let prop = obj as any;
        path.forEach((segment) => {
            prop = prop[segment as keyof typeof prop];
        });
        ret[path.pop() as keyof typeof ret] = prop;
    });
    return ret;
}

export function makePickable<T>(
    obj: T,
): <K extends readonly Leaves<T>[]>(
    keys: K | [],
) => { [K2 in K[number] as LastKey<K2>]: ResolvePath<T, K2> } {
    return (keys: any) => cherryPick(obj, keys) as any;
}
// store.mappers.ts
import { makePickable } from './pickable'
import store from './store'

export const mapDirectGetters = makePickable(store.getters);
export const mapDirectMutations = makePickable(store.commit);
export const mapDirectActions = makePickable(store.dispatch);

~Right now you have to import store and pass it as the first parameter~ (and getters have to be spread into data, not computed for the types to work), but I guess it's a good start.

Edit: I solved the issue of having to pass store as the first parameter. Now I just need to somehow make getters behave correctly, which I don't know how to do. But I think if we combined this with the internals of direct-vuex, we could create the mappers directly when in createDirectStore