Skip to content

Coder plugin: Create useCoderApiFunction custom hook #109

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

Closed
Parkreiner opened this issue Apr 8, 2024 · 0 comments
Closed

Coder plugin: Create useCoderApiFunction custom hook #109

Parkreiner opened this issue Apr 8, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@Parkreiner
Copy link
Member

Parkreiner commented Apr 8, 2024

Part of umbrella issue #16. Cannot be started until #108 and #110 are done.

Realistically, this is going to be, by far, the hardest part of the entire umbrella issue. Depending on how things go, we might have to abandon it, or turn it into something that gets chipped away at over time.

If we're not careful, I could see this using up a lot of working hours, which is why I think it should always be treated as a lower-priority issue. This could end up being on the level of our type-generation logic in the main Coder codebase. Expect to need to pay a visit to Matt Pocock's TypeScript Wizards Discord server for help

Problem

Let's say that #108 is done, and we have CoderClient.arbitraryApiCall defined, and ready to go. There's just one problem: the method isn't really type-safe at all. The method needs to be able to work with any kind of data, so it always return type Promise<any>, and while you can add type parameters to make things more specific, specificity doesn't translate to more accuracy. The type parameters rely entirely on user input – it's easy to make a typo and pass in the wrong parameter, and TypeScript won't let you know. You'll only realize your mistake after having a runtime error

So, why not make a wrapper over the method which will bring in that type safety? The core logic will still live in the method, but the hook will provide a lot of TypeScript magic to catch typos and other mistakes, while addressing some React-specific gotchas

Requirements

  • The hook does not have much runtime logic at all – the vast majority of it is deferred to CoderClient.arbitraryApiCall
    • Additional runtime values should only be introduced if it helps remove React gotchas and footguns
  • The "TS magic" is that the hook will accept a config object as input, and depending on the values in that input, that will inform the return type of the function you get back
    • For example:
     // Without a single type annotation, TypeScript knows that the return type is
     // Promise<Workspace>, that it needs a JSON body to make the workspace,
     // and that it needs a full endpoint that matches the pattern:
     // `/organizations/${string}/members/${string}/workspaces`
     // TypeScript will enforce this pattern and catch typos.
     
     // More importantly, the hook is aware of every method-endpoint combo, and
     // will reject anything it isn't aware of
     
    const makeWorkspace = useCoderApiFunction({
      method: "POST",
      endpoint: "/organizations/:organizationId/members/:memberId/workspaces"
    });
    
    // If anything differs from this precise arrangement of endpoint and body,
    // TypeScript will catch that. If we passed in an endpoint that doesn't have
    // dynamic parameter segments (defined via ":"), it would be smart enough
    const newWorkspace = await makeWorkspace({
      endpoint: `/organizations/${orgId}/members/${memberId}/workspaces`
      body: jsonBodyForWorkspaces,
    });
  • The hook only demands the bare minimum from the user – it only asks for information it doesn't already have
    • Put another way, if the hook's returned-out function can safely reuse any of the arguments the hook received as input, it should do that. The function should not require that information again when the user actually calls the function
  • The memory reference of the exported function should always remain stable, even if the inputs fed into it change across re-renders
    • Someone should be able to throw this into a useEffect call without (1) lying about their dependencies or (2) making the hook trigger any infinite loops

Possible solutions

  • One thing I've been toying with is a "lookup object type" that lets us kinda-sorta declaratively define our the body, return value, and endpoint for each body. I have a GH gist file that I've been throwing different attempts into.
    • My main worry is that this could be absolutely horrific for VS Code performance, and could slow down the entire client, whenever the user just happens to have the Coder plugin imported. See "Unknowns and Pitfalls" section below
  • We could figure out a way to generate every endpoint-method permutation via codegen.
    • Every permutation, and its associated hook inputs + API function inputs could be defined as more lightweight function overloads. This is most likely to scale, but we're getting into the same territory as the type generation we use for the main Coder codebase

Unknowns and pitfalls

  • If we're not careful with how the code is defined, this single hook could end up bringing VS Code's entire TypeScript LSP (Language Server Protocol) to its knees, and cause it to constantly crash
    • And because there is no way to interact with the type system directly, much less optimize it, there might not even be a good way to define this logic dynamically using traditional TypeScript types
    • If performance gets so bad that that traditional types would be impossible, we would either have to (1) give up on this, or (2) go the codegen route
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant