Skip to content
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

TypeScript helpers suggestion. #114

Closed
nickmessing opened this issue Apr 19, 2018 · 11 comments
Closed

TypeScript helpers suggestion. #114

nickmessing opened this issue Apr 19, 2018 · 11 comments
Labels

Comments

@nickmessing
Copy link

@nickmessing nickmessing commented Apr 19, 2018

Hello,

I have used feathers-vuex with TypeScript in a project and I made a set of helpers that I think could be really useful to other developers, here you can see the source code and below is an example of usage:

import Vue from 'vue'
import { Component } from 'vue-property-decorator'
import { namespace, Action } from 'vuex-class'
import { ActionGet, ActionCreate, ActionPatch } from '../types/feathers'
import { Person } from '../types/models'

const PeopleAction = namespace('people', Action)

@Component
export default class CreateEditPeople extends Vue {
  @PeopleAction('get') getPerson: ActionGet<Person>
  @PeopleAction('create') createPerson: ActionCreate<Person>
  @PeopleAction('patch') patchPerson: ActionPatch<Person>
  // some extra here
}

TypeScript reads it really well and all suggestions and autocomplete helps a lot.

@marshallswain

This comment has been minimized.

Copy link
Member

@marshallswain marshallswain commented Apr 21, 2018

Cool!

@nickmessing how would one use your feathers-vuex-helpers.ts file in their project? This looks like something that ought to go into the docs in a "Using with TypeScript" section. We can link to your helpers gist.

@nickmessing

This comment has been minimized.

Copy link
Author

@nickmessing nickmessing commented Apr 22, 2018

@marshallswain, I could finalize the typings and make a PR to make them universal so people can use them with bot class-component definition and classic component definition if you want to support that.

@marshallswain

This comment has been minimized.

Copy link
Member

@marshallswain marshallswain commented Apr 22, 2018

@marshallswain

This comment has been minimized.

Copy link
Member

@marshallswain marshallswain commented Jan 15, 2019

@rayfoss is your 👎 reaction a reflection of your dislike for TypeScript? Or is it about the quality of the recommendation?

@rayfoss

This comment has been minimized.

Copy link
Contributor

@rayfoss rayfoss commented Jan 15, 2019

@marshallswain Features should have a good reason to exist because they take time to add and maintain. My vote is just a reflection of this feature being 99% unlikely to be of use to me. I'm waiting for native types to come to Node. Fun fact, I used to use Typescript in Angular 2.

I think TS support is good for the project, as long as it doesn't get in the way for everybody else with a ton of irrelevant files and dependencies everywhere. Forcing it is definitely a bad idea, as there are many many hold outs and designers using js or flow. Remember coffeescript?

@marshallswain

This comment has been minimized.

Copy link
Member

@marshallswain marshallswain commented Jan 15, 2019

@rayfoss Thanks for the update. I have similar sentiment. If @nickmessing's suggestions work without requiring modifying the repo, that's the perfect solution, in my opinion.

I've added a link to this issue in the docs. I'm closing this issue, now. Thanks, guys.

@marshallswain

This comment has been minimized.

@NickBolles

This comment has been minimized.

Copy link
Contributor

@NickBolles NickBolles commented Jan 15, 2019

I have some typings for feathers-vuex too, I'm planning on putting together a PR at some point, but for now here they are.

Updated 7/30/19

import { Application, Query, Params } from "@feathersjs/feathers";
import { StoreOptions, Plugin, Commit, Dispatch, Store } from "vuex";
import Vue, { PluginObject, ComponentOptions } from "vue";
import { Request } from "request";
import { PropsDefinition } from "vue/types/options";

/**
 * default export
 */
declare const FeathersVuexDefault: (client: Application, options: Partial<FeathersVuexOptions>) => FeathersVuexResult;
export default FeathersVuexDefault;

interface FeathersVuexResult {
  service<ModelType = any, IDType = string>(servicePath: string, options?: Partial<FeathersVuexOptions>): Plugin<FeathersVuexServiceState<ModelType, IDType>>;
  auth<UserType>(options?: FeathersVuexAuthOptions): Plugin<FeathersVuexAuthState<UserType>>;
  FeathersVuex: PluginObject<any>;
}

interface FeathersVuexCoreProps {
  /**
   * @default 'id'
   */
  idField: string;
  /**
   * @default false
   */
  autoRemove: boolean;
  /**
   * @default true
   */
  enableEvents: boolean;
  /**
   * @default false
   */
  addOnUpsert: boolean;
  /**
   * @default false
   */
  diffOnPatch: boolean;
  /**
   * @default false
   */
  skipRequestIfExists: boolean;
  /**
   * @default false
   */
  preferUpdate: boolean;
  /**
   * @default false
   */
  replaceItems: boolean;
}
interface FeathersVuexStoreOptions<Store> extends Pick<StoreOptions<Store>, 'state' | 'getters' | 'mutations' | 'actions'> { }

interface FeathersVuexOptions<Store = any> extends FeathersVuexStoreOptions<Store>, FeathersVuexCoreProps {

  /**
   * @default 'path'
   */
  nameStyle?: 'short' | 'path';
  namespace: string;
  apiPrefix: string
  modelName: string;
  instanceDefaults?: any;
  /**
   * @debug false
   */
  debug: boolean;
}

/**
 * Service Model
 */

// This is an interface because it needs to return FeathersVuexModelClass & T
// from the constructor, which isn't allowed with classes
interface FeathersVuexModelClass<ModelType = any> {
  namespace: string;
  className: string;
  modelName: string;
  store: Store<any>; // todo: Type the VuexStore so we can include it here
  options: FeathersVuexOptions;
  new(data?: Partial<ModelType>, options?: FeathersVuexModelOptions): FeathersVuexModel<ModelType>;
  create: (params?: GetParams) => Promise<FeathersVuexModel<ModelType>>;
  save: (params?: GetParams) => Promise<FeathersVuexModel<ModelType>>;
  patch: (params?: GetParams) => Promise<FeathersVuexModel<ModelType>>;
  update: (params?: GetParams) => Promise<FeathersVuexModel<ModelType>>;
  clone: () => Promise<FeathersVuexModel<ModelType>>;
  reset: () => Promise<FeathersVuexModel<ModelType>>;
  remove: (params?: GetParams) => Promise<FeathersVuexModel<ModelType>>;
}

export type FeathersVuexModel<T> = FeathersVuexModelClass<T> & T;

type GetParams = Exclude<Params, 'paginate'>;
type FindParams = Params;

interface FeathersVuexModelOptions {
  isClone?: boolean;
  skipCommit?: boolean;
}

interface ModelStatic<T> {
  new(data?: Partial<T>, options?: FeathersVuexModelOptions): FeathersVuexModel<T>;
  find: (params?: FindParams) => FeathersVuexModel<T>[];
  findInStore: (params?: FindParams) => FeathersVuexModel<T>[];
  get: (id: any, params?: GetParams) => FeathersVuexModel<T>[];
  getFromStore: (id: any, params?: GetParams) => FeathersVuexModel<T>[];
}

/**
 * Vue Plugin
 */
type FeathersVuexGlobalModelsIndex<T> = {
  [P in keyof T]: {
    new(data?: Partial<T[P]>, options?: FeathersVuexModelOptions): FeathersVuexModel<T[P]>;
    find: (params?: FindParams) => FeathersVuexModel<T[P]>[];
    findInStore: (params?: FindParams) => FeathersVuexModel<T[P]>[];
    get: (id: any, params?: GetParams) => FeathersVuexModel<T[P]>[];
    getFromStore: (id: any, params?: GetParams) => FeathersVuexModel<T[P]>[];
  }
}


// This doesn't work because merging this into a string signature isn't a valid type
// Leaving it here so that hopefully someone can figure it out in the future
// type FeathersVuexGlobalModelsByPath<T> = {
//   byServicePath: FeathersVuexGlobalModelsIndex<T>
// }

/**
 * @description The type for the $FeathersVuex vue plugin. To type this stronger include the
 * following in a typings.d.ts file
 *
 *
 * @example
 * declare module "vue/types/vue" {
 *   import { FeathersVuexGlobalModels } from "feathers-vuex";
 *   interface Vue {
 *     $FeathersVuex: FeathersVuexGlobalModels<Services>;
 *   }
 * }
 *
 * @description Where services is something like this
 * @example
 * interface Services {
 *   users: User
 *   //...
 * }
 *
 * interface User {
 *   name: string;
 *   rating: number;
 *   //...
 * }
 *
 */
export type FeathersVuexGlobalModels<T = any, P = any> = FeathersVuexGlobalModelsIndex<T> // & FeathersVuexGlobalModelsByPath<P>; // See comment above

/**
 * Add FeathersVuex to the Vue prototype
 */
declare module "vue/types/vue" {
  interface FeathersVuexPluginType extends FeathersVuexGlobalModels { }

  interface Vue {
    $FeathersVuex: FeathersVuexPluginType;
  }
}

/**
 * Auth module factory
 */
interface FeathersVuexAuthOptions<State = FeathersVuexAuthState> extends FeathersVuexStoreOptions<State> {
  userService: string
}

interface FeathersVuexAuthState<UserType = any> {
  accessToken?: string, // The JWT
  payload: Object, // The JWT payload

  isAuthenticatePending: boolean,
  isLogoutPending: boolean,

  errorOnAuthenticate: Error | undefined,
  errorOnLogout: Error | undefined,
  user: UserType
}

interface FeathersVuexServiceState<ModelType = any, IDType = string> extends FeathersVuexCoreProps {
  ids: IDType[],
  keyedById: {
    [i: string]: ModelType
  }, // A hash map, keyed by id of each item
  currentId?: IDType, // The id of the item marked as current
  copy?: ModelType, // A deep copy of the current item
  servicePath: string // The full service path
  paginate: boolean, // Indicates if pagination is enabled on the Feathers service.
  pagination: { [key: string]: ModelType[] }, // Indicates if pagination is enabled on the Feathers service.

  isFindPending: boolean,
  isGetPending: boolean,
  isCreatePending: boolean,
  isUpdatePending: boolean,
  isPatchPending: boolean,
  isRemovePending: boolean,

  errorOnfind: Error | undefined,
  errorOnGet: Error | undefined,
  errorOnCreate: Error | undefined,
  errorOnUpdate: Error | undefined,
  errorOnPatch: Error | undefined,
  errorOnRemove: Error | undefined
}

/**
 * Data Components
 */
// todo: use generic FeathersVuex*Computed
export const FeathersVuexFind: ComponentOptions<
  Vue,
  FeathersVuexFindData,
  FeathersVuexFindMethods,
  FeathersVuexFindComputed,
  PropsDefinition<FeathersVuexFindProps>,
  FeathersVuexFindProps>;

export const FeathersVuexGet: ComponentOptions<
  Vue,
  FeathersVuexGetData,
  FeathersVuexGetMethods,
  FeathersVuexGetComputed,
  PropsDefinition<FeathersVuexGetProps>,
  FeathersVuexGetProps>;

interface FeathersVuexFindData {
  isFindPending: boolean;
}

interface FeathersVuexGetMethods {
  findData(): Promise<void>;
  fetchData(): Promise<void>;
}

interface FeathersVuexFindComputed<T = any> {
  items: T[];
  scope: { [key: string]: any }
  pagination: { [key: string]: any }
}

interface FeathersVuexDataComponentProps {
  service: string;
  query?: any;
  queryWhen?: boolean | ((id: string, ...args: any[]) => boolean);
  fetchQuery: any;
  watch?: string | string[];
  local?: boolean;
  editScope?: (params: FeathersVuexEditScopeArgs) => { [key: string]: any };
}

interface FeathersVuexEditScopeArgs {
  item: any;
  isGetPending: boolean
}

interface FeathersVuexFindProps extends FeathersVuexDataComponentProps {
  qid?: string;
}

interface FeathersVuexGetData {
  isFindPending: boolean;
  isGetPending: boolean;
}
interface FeathersVuexFindMethods {
  getArgs(queryToUse?: Query): any[];
  getData(): Promise<void>;
  fetchData(): Promise<void>;
}
interface FeathersVuexGetComputed<T = any> {
  item: T | null;
  scope: { [key: string]: any }
}

interface FeathersVuexGetProps extends FeathersVuexDataComponentProps {
  id?: number | string;
}


/**
 * Utilities
 */
// todo: these mixins could probably have much better return types to make use of the generic
export function makeFindMixin<T = any>(options: MakeFindMixinOptions<T>): (ComponentOptions<Vue> | typeof Vue);
export function makeGetMixin<T = any>(options: MakeGetMixinOptions<T>): (ComponentOptions<Vue> | typeof Vue);

interface DataComponentOptions {
  /**
   * Service name, either service or name are required
   *
   * note: it looks like this can also be a function, but I don't quite understand the effects
   * so I'm not documenting it right now
   */
  service: string;
  /**
   * Service name, either service or name are required
   */
  name?: string;
  params?: Params;
  /**
   * @default () => true
   */
  queryWhen?: boolean | ((params: Params) => boolean);
  /**
   * @default false
   */
  local?: boolean;
  qid?: string | number | symbol;
  debug?: boolean;
  watch: string | boolean | string[];
}

interface MakeFindMixinOptions<T = any> extends DataComponentOptions {
  fetchQuery?: Query;
  items?: T[];
}
interface MakeGetMixinOptions<T = any> extends DataComponentOptions {
  fetchParams?: Params;
  id?: any;
  item?: T;
}


/**
 * Init Auth
 */
interface InitAuthOptions {
  commit: Commit;
  dispatch: Dispatch;
  req: Request;
  cookieName: string;
  moduleName: string;
  feathersClient: Application;
}
export function initAuth(options: InitAuthOptions): Promise<object>;

@bartduisters

This comment has been minimized.

Copy link

@bartduisters bartduisters commented Jul 29, 2019

@NickBolles I just removed my client side project that used TypeScript, just to regenerate it as a JavaScript project.

Steps I tried to add typings to feathers-vuex:

  • Check if a @types/feathers-vuex definition exist, it does not.
  • Add my own typings directory to 'typeRoots' in tsconfig, specify a directory with the name feathers-vuex, with in it a index.d.ts with your code in it.
  • Renamed the 'declare module 'vue/types/vue' in your file to 'feathers-vuex' since that is what the cli told me to do (create a .d.ts file with a 'declare module "feathers-vuex";' in it. I then get an error that it can not be augmented. I delete all the code above 'declare module "vue/types/vue" in your file (which is "feathers-vuex" in my file). Then the error goes away. But it would still not recognize the typings.

Finally, I did what I said in the beginning and removed TypeScript just so I can follow along with a tutorial using this library.

@NickBolles

This comment has been minimized.

Copy link
Contributor

@NickBolles NickBolles commented Jul 30, 2019

@bartduisters There are probably other ways to achieve this without built in typings (which I believe are coming in v2), but here's how I have it setup

I have a folder @types/feathers-vuex with an index.d.ts in it with that content from my post above (which I just updated). Then I have a file @types/feathers-vuex-app.d.ts with the following content in it.

import { Services } from "../store";
import { FeathersVuexGlobalModels } from "feathers-vuex";

declare module "vue/types/vue" {
  interface FeathersVuexPluginType extends FeathersVuexGlobalModels<Services> { } 
}

This will extend the Vue prototype typings with

This file is picked up by typescript by declaring it in the "typeRoots" in the tsconfig.json.

{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types/",
      "./client/@types/"
    ],
  }
}

where store/index.ts contains this (and more). You should probably break this into it's own file actually, but I have it in store/index.ts along with my store content. Also this is importing the user service from my feathers server, which was generated by feathers-plus cli. It could simply be an interface that you define though.

import { User } from "../../server/services/v1/users/users.interface";
export interface Services {
  users: User;
}
@bartduisters

This comment has been minimized.

Copy link

@bartduisters bartduisters commented Jul 31, 2019

Thanks for the clarification. Will try it as soon as I try another project with TypeScript and feathers-vuex!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.