-
Notifications
You must be signed in to change notification settings - Fork 0
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
Swappable operation implementations #115
Conversation
f8a626d
to
9f881ba
Compare
4c84529
to
52cb799
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great in general!
packages/executors/package.json
Outdated
"author": "Amazee Labs <[email protected]>", | ||
"license": "ISC", | ||
"dependencies": { | ||
"@types/lodash-es": "^4.17.12", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
devDependencies
?
packages/executors/src/lib.ts
Outdated
export function registerExecutor( | ||
id: string, | ||
variables: VariablesMatcher, | ||
executor: Executor, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TS function overloading does not always play well. For example, guess the inferred types:
type FirstParam = Parameters<typeof registerExecutor>[0];
type LastParam = Parameters<typeof registerExecutor>[2];
I would suggest using a single object parameter:
export function registerExecutor(args: RegistryEntry) {
registry.push(args);
}
IMHO it would make make the function calls easier to read:
registerExecutor({ executor: myFunc, id: 'foo-bar' });
Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would produce a more natural API:
// register an executor that matches everything
registerExecutor(iDoEverythingOverHttp);
// register one for a specific operation;
registerExecutor(MyOperationId, myOperationImplementation);
// for a specific set of input:
registerExecutor(ContentHubQuery, {page: 1}, staticFirstPage);
Compared to:
// register an executor that matches everything
registerExecutor({executor: iDoEverythingOverHttp});
// register one for a specific operation;
registerExecutor({id: MyOperationId, executor: myOperationImplementation});
// for a specific set of input:
registerExecutor({id:ContentHubQuery, args: {page: 1}, executor: staticFirstPage});
I'm not sure if it has to be possible to infer types here, since theOperation*
type helpers are available to infer return types and variables:
type first = AnyOperationId;
type second = Partial<OperationVariables<first>>;
type third = () => Promise<OperationResult<first>>;
But I also don't have a very strong opinion on this. How strong are yours? Should we pull in more voices?
packages/executors/src/lib.ts
Outdated
* @param variables | ||
* A dictionary of variables to be passed to the operation. | ||
*/ | ||
export function createExecutor(id: string, variables?: Record<string, any>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the return type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, in my pet projects I use '@typescript-eslint/explicit-function-return-type': 'error'
eslint rule. But not sure if it will play well at work 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats a great discussion point. Explicit means a type error is found earlier, not potentially through three levels of generic functions that infer something.
Description of changes
@amazeelabs/executors
allows to register execution functions for specific GraphQL operations and parameter combinations.useOperation
and this new system under the hood.Motivation and context
This approach solves two problems:
useOperation
. This binds the UI to the implementation and makes it harder to use components in other environments (e.g. previews). Now every data operation has a GraphQL operation in the schema package and the UI component always usesuseOperation
and the implementation (Gatsby, Decap, Next ...) controls how the query is resolved.Use cases:
How has this been tested?