Skip to content

Coder plugin: Add arbitraryApiCall method to CoderClient API factory #108

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
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 #107 is done.

This should hopefully be a straightforward (and even quick) update.

Problem

One of the biggest wins we can get for the Coder plugin is making it so that using the plugin doesn't feel so walled-off. We still intend to ship polished UI experiences for our main components, but users will inevitably have use cases that we can't reasonably account for in the UI. So, in that case, why not give them access to the full Coder API, and give them the power to wire things up themselves?

Requirements

  • A method defined on the CoderClient class that lets users call any Coder API endpoint that they want, but that still has some restrictions and niceties
    • The method will, by necessity, have to return the any type, but beyond that, it should be as type-safe as possible
    • It should only make the user pass in what is absolutely necessary. We should do as much behind the scenes as possible
      • The user shouldn't have to worry about handling auth logic themselves
      • If preflight checks ever become necessary, the user shouldn't need to worry about those either
  • All the Coder types from NPM should be easy to plug into the method as type parameters
  • Method should be defined in a way to ensure that it can't ever lose its this context when passed around as a value in the React UI
  • It should be easy to use this method as a building block for a useCoderApiFunction hook
    • That is, this method should be designed in a way that it provides the core logic for the hook out of the box, just without any types. Then it will be the hook's responsibility to provide those types, and turn the method into something type-safe

Possible solution

Let's say that we have a general-purpose ReadonlyJsonValue type that represents any valid JSON-serializable value:

type ReadonlyJsonValue =
  | string
  | number
  | boolean
  | null
  | readonly ReadonlyJsonValue[]
  | Readonly<{ [key: string]: ReadonlyJsonValue }>;

In that case, we can define a method like this:

type ArbitraryApiInput <
  TBody extends ReadonlyJsonValue = ReadonlyJsonValue,
> = Readonly<{
  // Very niche syntax, but this means that 'method' will have type 'string',
  // but the other predefined methods will still show up in autocomplete
  method: "GET" | "POST" | "PUT" | "DELETE" | (string & {});
  
  // Ensures that each endpoint at least starts with a '/'
  endpoint: `/${string}`;
  
  // Genericized body type parameter
  body: TBody;
  
  // Lets user override default request init (within reason); should not
  // let the user accidentally override things like Coder auth token
  init?: Partial<RequestInit>;
}>;

type CoderClient = {
  // <-- Other existing properties/methods go here -->
  
  arbitraryApiCall<
    TReturn = any
    TBody extends ReadonlyJsonValue = ReadonlyJsonValue,
  > = (input: ArbitraryApiInput<TBody>) => Promise<TReturn>;
}

Code example

// Unsafe version
function CustomComponent () {
  // Custom hook will be made as part of issue #107
  const client = useCoderClient();
  
  const onSubmit = async (event) => {
    // Pretend that data is retrieved via the FormData API
    const newWorkspace = await client.arbitraryApiCall({
      method: "POST",
      endpoint: `/organizations/${organizationId}/members/${memberId}/workspaces`,
      body: {
        name: newWorkspaceName,
        // Other properties go here
      },
    });
    
    // Do something new newly-created workspace here; workspace
    // is of type any
  };
  
  return (
    <form onSubmit={onSubmit}>
      // Form elements go here
    </form>
  );
}

// "Safer" version is virtually identical, but this time, the user has used type
// parameters
const newWorkspace = await client.arbitraryApiCall<Workspace>({
  // Same runtime arguments as before
});

// At this point, newWorkspace is of type Workspace, which may or may not be
// accurate. It's up to the user to wire things up correctly
@coder-labeler coder-labeler bot added the enhancement New feature or request label Apr 8, 2024
@Parkreiner Parkreiner changed the title Coder plugin: Add arbitraryApiCall method to CoderClient Coder plugin: Add arbitraryApiCall method to CoderClient API factory Apr 8, 2024
@Parkreiner Parkreiner closed this as not planned Won't fix, can't repro, duplicate, stale May 28, 2024
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