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: improve UseCase#execute typing #107

Open
azu opened this Issue Feb 25, 2017 · 11 comments

Comments

Projects
None yet
2 participants
@azu
Member

azu commented Feb 25, 2017

From #101 (comment)

Currently, Following pattern is pass.
We want to improve this.

class UseCase extends UseCase {
    execute(value: string) {
        // do some
    }
}
const useCase = new UseCase();
// FIXME: This execute(number) is pass!
context.useCase(useCase).execute(42).then(() => {
  
})
@azu

This comment has been minimized.

Show comment
Hide comment
@azu
Member

azu commented Mar 27, 2017

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Mar 28, 2017

Member

Can we make it?

type UseCases = AUseCase | BUseCase;
const context = new Context<UseCases>();
context.useCase(new AUseCase).execute(1,2,3);
Member

azu commented Mar 28, 2017

Can we make it?

type UseCases = AUseCase | BUseCase;
const context = new Context<UseCases>();
context.useCase(new AUseCase).execute(1,2,3);
@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Mar 29, 2017

Member

Status 2017-03-29

I have a step-by-step plan to resolve this issue.

Step 1. Add generics to context.useCase<UseCaseArgs>(useCase).execute(arg) #137

The user can write following:

type ParentUseCaseArgs = string;
class ParentUseCase extends UseCase {
    execute(value: ParentUseCaseArgs) {
        return this.context.useCase(new ChildUseCase()).execute(value);
    }
}
context.useCase(parentUseCase).execute<ParentUseCaseArgs>("arg").then(() => {
    const state = context.getState<StoreState>();
    console.log(state.A.a);
    console.log(state.B.b);
}).catch((error: Error) => {
    console.error(error);
});

But, it is lengthy syntax.

Step 2: if TypeScript implement Variadic Kinds, We can move to that.

The user can write following:

class ParentUseCase extends UseCase {
    execute(value: string) {
        return this.context.useCase(new ChildUseCase()).execute(value);
    }
}
context.useCase(parentUseCase).execute("arg").then(() => {
    const state = context.getState<StoreState>();
    console.log(state.A.a);
    console.log(state.B.b);
}).catch((error: Error) => {
    console.error(error);
});
Member

azu commented Mar 29, 2017

Status 2017-03-29

I have a step-by-step plan to resolve this issue.

Step 1. Add generics to context.useCase<UseCaseArgs>(useCase).execute(arg) #137

The user can write following:

type ParentUseCaseArgs = string;
class ParentUseCase extends UseCase {
    execute(value: ParentUseCaseArgs) {
        return this.context.useCase(new ChildUseCase()).execute(value);
    }
}
context.useCase(parentUseCase).execute<ParentUseCaseArgs>("arg").then(() => {
    const state = context.getState<StoreState>();
    console.log(state.A.a);
    console.log(state.B.b);
}).catch((error: Error) => {
    console.error(error);
});

But, it is lengthy syntax.

Step 2: if TypeScript implement Variadic Kinds, We can move to that.

The user can write following:

class ParentUseCase extends UseCase {
    execute(value: string) {
        return this.context.useCase(new ChildUseCase()).execute(value);
    }
}
context.useCase(parentUseCase).execute("arg").then(() => {
    const state = context.getState<StoreState>();
    console.log(state.A.a);
    console.log(state.B.b);
}).catch((error: Error) => {
    console.error(error);
});
@kimamula

This comment has been minimized.

Show comment
Hide comment
@kimamula

kimamula Apr 2, 2017

A current workaround for the variadic kinds I think of is something like the following.

interface UseCaseBase {
    // define UseCase's methods and properties other than execute
    foo(): string;
}
interface UseCase0 extends UseCaseBase {
    execute(): any;
}
interface UseCase1<T1> extends UseCaseBase {
    execute(arg1: T1): any;
}
interface UseCase2<T1, T2> extends UseCaseBase {
    execute(arg1: T1, arg2: T2): any;
}
// ... create UseCaseX as many as you want

interface UseCaseConstructor {
    new (): UseCase0;
    new<T1> (): UseCase1<T1>;
    new<T1, T2> (): UseCase2<T1, T2>;
}

// Implementation of UseCase
abstract class _UseCase {
    abstract execute(...args: any[]): any;
    foo() {
        return '';
    }
}

// Cast UseCase
const UseCase = _UseCase as UseCaseConstructor;

class SomeUseCase extends UseCase {
    execute() {}
}

class AnotherUseCase extends UseCase<string> {
    execute(arg1: string) {}
}

class YetAnotherUseCase extends UseCase<string, number> {
    execute(arg1: string, arg2: number) {}
}

class Context {
    useCase(useCase: UseCase0): { execute(): Promise<void> };
    useCase<T1>(useCase: UseCase1<T1>): { execute(arg1: T1): Promise<void> };
    useCase<T1, T2>(useCase: UseCase2<T1, T2>): { execute(arg1: T1, arg2: T2): Promise<void> };
    useCase(useCase: { execute(...args: any[]): any }): { execute(...args: any[]): Promise<void> } {
        // todo implement
    }
}

http://bit.ly/2opX4l2

What do you think of it?

kimamula commented Apr 2, 2017

A current workaround for the variadic kinds I think of is something like the following.

interface UseCaseBase {
    // define UseCase's methods and properties other than execute
    foo(): string;
}
interface UseCase0 extends UseCaseBase {
    execute(): any;
}
interface UseCase1<T1> extends UseCaseBase {
    execute(arg1: T1): any;
}
interface UseCase2<T1, T2> extends UseCaseBase {
    execute(arg1: T1, arg2: T2): any;
}
// ... create UseCaseX as many as you want

interface UseCaseConstructor {
    new (): UseCase0;
    new<T1> (): UseCase1<T1>;
    new<T1, T2> (): UseCase2<T1, T2>;
}

// Implementation of UseCase
abstract class _UseCase {
    abstract execute(...args: any[]): any;
    foo() {
        return '';
    }
}

// Cast UseCase
const UseCase = _UseCase as UseCaseConstructor;

class SomeUseCase extends UseCase {
    execute() {}
}

class AnotherUseCase extends UseCase<string> {
    execute(arg1: string) {}
}

class YetAnotherUseCase extends UseCase<string, number> {
    execute(arg1: string, arg2: number) {}
}

class Context {
    useCase(useCase: UseCase0): { execute(): Promise<void> };
    useCase<T1>(useCase: UseCase1<T1>): { execute(arg1: T1): Promise<void> };
    useCase<T1, T2>(useCase: UseCase2<T1, T2>): { execute(arg1: T1, arg2: T2): Promise<void> };
    useCase(useCase: { execute(...args: any[]): any }): { execute(...args: any[]): Promise<void> } {
        // todo implement
    }
}

http://bit.ly/2opX4l2

What do you think of it?

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Apr 3, 2017

Member

@kimamula Thanks for interesting idea.

Member

azu commented Apr 3, 2017

@kimamula Thanks for interesting idea.

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Sep 24, 2017

Member

Land Variadic types on Roadmap(future).

Member

azu commented Sep 24, 2017

Land Variadic types on Roadmap(future).

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Jan 27, 2018

Member

Currently, almin supports executor method #193
This executor is type-safe method.

executor(executor: (useCase: Pick<T, "execute">) => any): Promise<void>;

Example:

context.useCase(new MyUseCase())
 .executor(useCase => useCase.execute("value"))
 .then(() => {
   console.log("test");
 });

📝 Document: https://almin.js.org/docs/en/usecaseexecutor-api.html#executorexecutor-usecase-pick-t-execute-any-promise-void

However, executor is redundancy.
We will continue to investigate this issue.

Member

azu commented Jan 27, 2018

Currently, almin supports executor method #193
This executor is type-safe method.

executor(executor: (useCase: Pick<T, "execute">) => any): Promise<void>;

Example:

context.useCase(new MyUseCase())
 .executor(useCase => useCase.execute("value"))
 .then(() => {
   console.log("test");
 });

📝 Document: https://almin.js.org/docs/en/usecaseexecutor-api.html#executorexecutor-usecase-pick-t-execute-any-promise-void

However, executor is redundancy.
We will continue to investigate this issue.

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Feb 13, 2018

Member

Maybe, We can realive execute() typing by conditional types.

// rough note
export interface UseCase<T>{
  execute(args: T);
}

type ResolveUseCaseArgType<T> =
  T extends UseCase<infer R> ? R : never;

class Context {
    useCase<T extends UseCase>(useCase: T): UseCaseExecutor<T>;
      type UseCaseArgs = ResolveUseCaseArgType<T>;
      return new UseCaseExecutor<UseCaseArgs>(useCase);
    }
}
Member

azu commented Feb 13, 2018

Maybe, We can realive execute() typing by conditional types.

// rough note
export interface UseCase<T>{
  execute(args: T);
}

type ResolveUseCaseArgType<T> =
  T extends UseCase<infer R> ? R : never;

class Context {
    useCase<T extends UseCase>(useCase: T): UseCaseExecutor<T>;
      type UseCaseArgs = ResolveUseCaseArgType<T>;
      return new UseCaseExecutor<UseCaseArgs>(useCase);
    }
}
@azu

This comment has been minimized.

Show comment
Hide comment
Member

azu commented Mar 2, 2018

@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Apr 27, 2018

Member

feat(almin): Support context.useCase#execute typing #342

I've implemented type-check of execute method in #342
But, it is has a drawback again to executor method.

It it a limitation of execute typing.

executor()

import { UseCase, Context } from "almin";
class MyUseCaseA extends UseCase {
    execute(_a: string) {}
}
const context = new Context({
    store: createStore({ name: "test" })
});

// valid
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute("A")); 
// invalid
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute()); // no argument
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute(1)); // can not pass number
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute("A",  42)); // require 1 argument, but specify 2 arguments

execute()

import { UseCase, Context } from "almin";
class MyUseCaseA extends UseCase {
    execute(_a: string) {}
}
const context = new Context({
    store: createStore({ name: "test" })
});

// valid
context.useCase(new MyUseCaseA()).execute("A");
// invalid
context.useCase(new MyUseCaseA()).execute(); // no argument
context.useCase(new MyUseCaseA()).execute(1); // can not pass number
// **valid in almin 0.17**
// BUT, It should be error 
context.useCase(new MyUseCaseA()).execute("A". 42);
Member

azu commented Apr 27, 2018

feat(almin): Support context.useCase#execute typing #342

I've implemented type-check of execute method in #342
But, it is has a drawback again to executor method.

It it a limitation of execute typing.

executor()

import { UseCase, Context } from "almin";
class MyUseCaseA extends UseCase {
    execute(_a: string) {}
}
const context = new Context({
    store: createStore({ name: "test" })
});

// valid
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute("A")); 
// invalid
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute()); // no argument
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute(1)); // can not pass number
context.useCase(new MyUseCaseA()).executor(useCase => useCase.execute("A",  42)); // require 1 argument, but specify 2 arguments

execute()

import { UseCase, Context } from "almin";
class MyUseCaseA extends UseCase {
    execute(_a: string) {}
}
const context = new Context({
    store: createStore({ name: "test" })
});

// valid
context.useCase(new MyUseCaseA()).execute("A");
// invalid
context.useCase(new MyUseCaseA()).execute(); // no argument
context.useCase(new MyUseCaseA()).execute(1); // can not pass number
// **valid in almin 0.17**
// BUT, It should be error 
context.useCase(new MyUseCaseA()).execute("A". 42);
@azu

This comment has been minimized.

Show comment
Hide comment
@azu

azu Jul 14, 2018

Member

TypeScript 3.0 will work for execute method completely.

Extracting and spreading parameter lists with tuples
https://blogs.msdn.microsoft.com/typescript/2018/07/12/announcing-typescript-3-0-rc/#tuples-and-parameters

Member

azu commented Jul 14, 2018

TypeScript 3.0 will work for execute method completely.

Extracting and spreading parameter lists with tuples
https://blogs.msdn.microsoft.com/typescript/2018/07/12/announcing-typescript-3-0-rc/#tuples-and-parameters

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment