maoberlehner / vuex-map-fields

Enable two-way data binding for form fields saved in a Vuex store
MIT License
1.41k stars 85 forks source link

Add .d.ts for typescript like vuex #26

Open henrixapp opened 6 years ago

henrixapp commented 6 years ago

Adding a index.d.ts would be great in order to support typescript. I have drafted one already, based on the vuex one:

declare module 'vuex-map-fields' {
import _Vue, { WatchOptions } from "vue";

// augment typings of Vue.js

export  class Store<S> {
  constructor(options: StoreOptions<S>);

  readonly state: S;
  readonly getters: any;

  replaceState(state: S): void;

  dispatch: Dispatch;
  commit: Commit;

  subscribe<P extends MutationPayload>(fn: (mutation: P, state: S) => any): () => void;
  watch<T>(getter: (state: S) => T, cb: (value: T, oldValue: T) => void, options?: WatchOptions): () => void;

  registerModule<T>(path: string, module: Module<T, S>, options?: ModuleOptions): void;
  registerModule<T>(path: string[], module: Module<T, S>, options?: ModuleOptions): void;

  unregisterModule(path: string): void;
  unregisterModule(path: string[]): void;

  hotUpdate(options: {
    actions?: ActionTree<S, S>;
    mutations?: MutationTree<S>;
    getters?: GetterTree<S, S>;
    modules?: ModuleTree<S>;
  }): void;
}

export  function install(Vue: typeof _Vue): void;

export interface Dispatch {
  (type: string, payload?: any, options?: DispatchOptions): Promise<any>;
  <P extends Payload>(payloadWithType: P, options?: DispatchOptions): Promise<any>;
}

export interface Commit {
  (type: string, payload?: any, options?: CommitOptions): void;
  <P extends Payload>(payloadWithType: P, options?: CommitOptions): void;
}

export interface ActionContext<S, R> {
  dispatch: Dispatch;
  commit: Commit;
  state: S;
  getters: any;
  rootState: R;
  rootGetters: any;
}

export interface Payload {
  type: string;
}

export interface MutationPayload extends Payload {
  payload: any;
}

export interface DispatchOptions {
  root?: boolean;
}

export interface CommitOptions {
  silent?: boolean;
  root?: boolean;
}

export interface StoreOptions<S> {
  state?: S;
  getters?: GetterTree<S, S>;
  actions?: ActionTree<S, S>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<S>;
  plugins?: Plugin<S>[];
  strict?: boolean;
}

type ActionHandler<S, R> = (injectee: ActionContext<S, R>, payload: any) => any;
interface ActionObject<S, R> {
  root?: boolean;
  handler: ActionHandler<S, R>;
}

export type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;
export type Action<S, R> = ActionHandler<S, R> | ActionObject<S, R>;
export type Mutation<S> = (state: S, payload: any) => any;
export type Plugin<S> = (store: Store<S>) => any;

export interface Module<S, R> {
  namespaced?: boolean;
  state?: S | (() => S);
  getters?: GetterTree<S, R>;
  actions?: ActionTree<S, R>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<R>;
}

export interface ModuleOptions{
  preserveState?: boolean
}

export interface GetterTree<S, R> {
  [key: string]: Getter<S, R>;
}

export interface ActionTree<S, R> {
  [key: string]: Action<S, R>;
}

export interface MutationTree<S> {
  [key: string]: Mutation<S>;
}

export interface ModuleTree<R> {
  [key: string]: Module<any, R>;
}

 const _default: {
  Store: typeof Store;
  install: typeof install;
}
type Dictionary<T> = { [key: string]: T };
type Computed = () => any;
type MutationMethod = (...args: any[]) => void;
type ActionMethod = (...args: any[]) => Promise<any>;

interface Mapper<R> {
  (map: string[]): Dictionary<R>;
  (map: Dictionary<string>): Dictionary<R>;
}

interface MapperWithNamespace<R> {
  (namespace: string, map: string[]): Dictionary<R>;
  (namespace: string, map: Dictionary<string>): Dictionary<R>;
}

interface FunctionMapper<F, R> {
  (map: Dictionary<(this: typeof _Vue, fn: F, ...args: any[]) => any>): Dictionary<R>;
}

interface FunctionMapperWithNamespace<F, R> {
  (
    namespace: string,
    map: Dictionary<(this: typeof _Vue, fn: F, ...args: any[]) => any>
  ): Dictionary<R>;
}

interface MapperForState {
  <S>(
    map: Dictionary<(this: typeof _Vue, state: S, getters: any) => any>
  ): Dictionary<Computed>;
}

interface MapperForStateWithNamespace {
  <S>(
    namespace: string,
    map: Dictionary<(this: typeof _Vue, state: S, getters: any) => any>
  ): Dictionary<Computed>;
}

interface NamespacedMappers {
  mapState: Mapper<Computed> & MapperForState;
  mapMutations: Mapper<MutationMethod> & FunctionMapper<Commit, MutationMethod>;
  mapFields: Mapper<Computed>;
  mapActions: Mapper<ActionMethod> & FunctionMapper<Dispatch, ActionMethod>;
}

export  const mapState: Mapper<Computed>
  & MapperWithNamespace<Computed>
  & MapperForState
  & MapperForStateWithNamespace;

export  const mapMutations: Mapper<MutationMethod>
  & MapperWithNamespace<MutationMethod>
  & FunctionMapper<Commit, MutationMethod>
  & FunctionMapperWithNamespace<Commit, MutationMethod>;

export  const mapGetters: Mapper<Computed>
  & MapperWithNamespace<Computed>;

export  const mapActions: Mapper<ActionMethod>
  & MapperWithNamespace<ActionMethod>
  & FunctionMapper<Dispatch, ActionMethod>
  & FunctionMapperWithNamespace<Dispatch, ActionMethod>;

interface HelperOptions{
  getterType: string;
  mutationType:string
}
export  function createHelpers(helperOptions:HelperOptions): NamespacedMappers;

export default _default;
}

Probably the unused types should be removed. Currently it just enables correct typing with createHelpers

maoberlehner commented 6 years ago

Hey @henrixapp I appreciate your work and from using TypeScript myself for certain projects, I know how nice it is to have the correct types for all of your dependencies.

But what concerns me is this: the types have to be updated every time I make some changes – and to be completely honest with you: I don't want to invest my time in that. It even gets more complicated if other people decide to contribute their time, I can't expect every contributor to be familiar with TypeScript.

So I decided against adding types in this repository. I'm sorry.

But I'd be very happy if you (or anyone else) decides to invest some time and add vuex-map-fields to https://github.com/DefinitelyTyped/DefinitelyTyped

Thx for contributing!

vemmkof commented 4 years ago

The other way to add v-m-f with ts is:

import Vue from "vue";
import Vuex from "vuex";
const vuexMapFields = require("vuex-map-fields");
const getField = vuexMapFields.getField;
const updateField = vuexMapFields.updateField;
Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  getters: { getField },
  mutations: { updateField },
  actions: {},
  modules: {}
});
mchl18 commented 3 years ago

Hey @maoberlehner !

It seems there is a simpler way to get typings in than to add them to DefinitelyTyped, which has its whole own process and standards.

Instead it is recommended by them that, if there is a way to generate these automatically, to just add them to the npm repo:

If you are the library author and your package is written in TypeScript, bundle the autogenerated declaration files in your package instead of publishing to Definitely Typed.

source: https://github.com/DefinitelyTyped/DefinitelyTyped#create-a-new-package

However this can also be done with JS. For generating these typings we just need to add typescript to the devDependencies and add a step in the build scripts which runs typescript with a special config. After that, the type definitions will be provided simply from within the folder node_modules/vuex-map-fields/types instead of node_modules/@types/vuex-map-fields, which I believe is much simpler. We just reference that folder in the package.json

All of which has been done here:

https://github.com/maoberlehner/vuex-map-fields/pull/137

The auto-generated typings are currently not really the most precise as they will just default to any in most cases. I believe it is possible to add and precisely generate these via jsDoc somehow, which is something which would need to be done in order to serve meaningful types. That is something which has not been done in this PR yet.

This PR way however avoids needing to create a shim d.ts to avoid errors when the vue project has been set up with typescript, so I would argue it is a step into the right direction.