2

I have the following class

class StoreBuilder<TStore> {
  private _map: Map<string, Function>
  private _selectors: Map<string, Function>
  
  constructor(){
    this._map = new Map<string, Function>()
    this._selectors =  new Map<string, Function>() 
  }


  add<TArg>(kind: string, code: (s:TStore, x:TArg)=>void){
    if(!this._map.has(kind)){
      const func = (store: TStore, a0: TArg)=>{
          code(store, a0)
      }
      this._map.set(kind, func)
    }

    if(!this._selectors.has(kind)){
      const func = (arg: TArg)=>{
        return {type: kind, payload: arg}
      }
      this._selectors.set(kind, func)
    }

  }

  build(){

    const reducer = (store: TStore, action: {type: string, payload: any})=>{
      if(this._map.has(action.type)){
       try {
        const code = this._map.get(action.type)
        code(store, action.payload)
       } catch (err) {
         console.error(err)
       }
      }
    }

    const commands = new Object()
    for(const [name, func] of this._selectors){
      commands[name] = func
    }

    const ret = { commands, reducer}
    return ret
  }

}

this is how I'm using it

type mystoretype ={
  coins: number
}

const coiner = new StoreBuilder<mystoretype>()

coiner.add<number>('addCoin', (s,x)=>{
  s.coins += x
})

coiner.add<number>('takeCoin', (s,x)=>{
  if(s.coins>x){
    s.coins-= x
  }
})

const {commands, reducer} = coiner.build()

but when I do this, works in javascript but gives error in typescript:

const cmd1 = commands.addCoin(23) //Property 'addCoin' does not exist on type 'Object'.ts(2339)
const cmd2 = commands.takeCoin(34) //Property 'takeCoin' does not exist on type 'Object'.ts(2339)

1 Answer 1

3

I didn't know the answer myself, but I was intrigued to find a solution.

I don't think you can use your current syntax to achieve this result.
However, if you'd be open to modifying the way the builder is created to a Fluent Interface using Method chaining, then the following might be an option.

Note: I've simplified your example and removed the actual implementation to increase readability of the core idea. Hopefully this helps to get the idea accross.

interface Builder<TStore, TType = {}> {
  add<TName extends string, TArg>(key: TName, action: (store: TStore, param: TArg) => void): Builder<TStore, TType & { [key in TName]: (param: TArg) => void }>;
  build: () => TType;
}

// Example - Creation and usage
type mystoretype ={
  coins: number
};

const builder = {} as Builder<mystoretype>; // Using your existing implementation
const instance = builder
  .add("addCoin", (s, x: number) => { s.coins += x })
  .add("takeCoin", (s, x: string) => {})
  .build();

instance.addCoin(12);
instance.takeCoin("Abc");

// Example of errors
instance.addCoin("Abc"); // Error: as a string is not a number
instance.takeCoin(12); // Error: as a number is not a string

Typescript sandbox

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.