Search code examples
typescripturldesign-patternsbuilder

Type custom URLBuilder with typescript


I've been trying for a while to create a custom ( typed with typescript ) URLBuilder (with builder design pattern) functionality, which I want to design to be as reusable as possible and right now I'm stuck with the first part of the URL, which is the path.

The query part was a bit easier and I managed to do it, but the path part is harder than expected for me.

So what is the idea:

1. The first way is by passing the endpoints directly to the constructor:

type Path = {
  path1: "fixtures" | "teams";
  path2: { fixtures: "statistics" | "lineups"; teams: "seasons" | "countries" };
};

class URLMultiplePaths<P extends Path> {
  constructor(path1: P["path1"], path2: P["path2"][P["path1"]]) {}

  setPath(path: P) {
    return this;
  }
}

const url = new URLMultiplePaths("fixtures", "");

I want every next path to be typed depending on the previous one, and when press ctr + space inside the quotes when passing arguments, to get only the options that are typed depending on the path before.

So if I set the path1 to be fixtures the options for the next path should be statistics or lineups and if I set the path1 to be teams the options for the path2 to be seasons or countries

And this - path2: P["path2"][P["path1"]] in theory should work, but typescript can not infer the type from that lie of code.

Keep in mind that the goal is for this builder to work with different number of params...

2. The second way is by passing the endpoints to the setPath method: this way I can chain the paths one by one. But I can't figure out how every next path to be typed depending on the previous one. So that is why I'm trying to implement the example above and then I can think of implementing it with the method and chaining of paths.

But if you have an idea about the second one please do share I think it would be a bit better and maybe cleaner.


Solution

  • I think you can do this with conditional types:

    type FixturePath = "fixtures";
    type TeamsPath = "teams";
    type OtherPath = "other";
    
    type GenericPath<T extends FixturePath | TeamsPath | OtherPath> = {
        path1: T;
        path2: T extends FixturePath ? "statistics" | "lineups" :
            T extends TeamsPath ? "seasons" | "countries" :
                T extends OtherPath ? "foo" | "bar" : any;
    };
    
    // if you don't want the generic type parameter:
    type Path = GenericPath<FixturePath> | GenericPath<TeamsPath> | GenericPath<OtherPath>;
    
    const url1: GenericPath<TeamsPath> = {path1: "teams", path2: "seasons"}
    const url2: Path = {path1: "teams", path2: "seasons"}