Felix
Felix

Reputation: 33

Fluent API with TypeScript?

I am trying to develop a fluent api in TypeScript, which I would like to use as follows:

export interface Person {
    firstName: string;
    lastName: string;
}

new Builder<Person>()
    .func1("stringArg1")
    .func2("firstName")
    .func3("stringArg2");

func2 may only accept names of properties that actually occur in the specified interface (here: Person). Furthermore, func2 shall only be available at the return value of func1 and func3 only at the return value of func2. The sequence of function calls must be observed in any case. The class Builder must be able to evaluate the specified values at any time (stringArg1, firstName and stringArg2).

I would be very grateful for advices and help.

Upvotes: 0

Views: 4207

Answers (2)

jcalz
jcalz

Reputation: 328097

One possible implementation of what you're asking for could be something like this:

class Builder<T> {
    func1 = (f1arg: string) => ({
        func2: (f2arg: keyof T) => ({
            func3: (f3arg: string) => ({
                // something here?
            })
        })
    })
}

Here though we are not maintaining a single class instance of Builder. Once you call func1(), you get a new object with a func2 property. When you're all done, func3() can return whatever you'd like (and it has access to the arguments due to being closed over them), including an instance of Builder if you want. Depending on what you're looking for, this might work.


The more normal case of a Builder class is something that, after each method, returns the same instance of that class. That's easy enough to write if you don't have restrictions on what methods can be called when. If you do have such restrictions, you can tell the compiler this, but it's a bit complicated looking. Here's one possible way to do it:

type Builder0<T> = Omit<_Builder<T>, "func2" | "func3">;
type Builder1<T> = Omit<_Builder<T>, "func1" | "func3">;
type Builder2<T> = Omit<_Builder<T>, "func1" | "func2">;
type Builder<T> = Omit<_Builder<T>, "func1" | "func2" | "func3">;
class _Builder<T> {
    f1arg?: string;
    f2arg?: keyof T;
    f3arg?: string;
    func1(f1arg: string): Builder1<T> {
        this.f1arg = f1arg;
        return this;
    }
    func2(f2arg: keyof T): Builder2<T> {
        this.f2arg = f2arg;
        return this;
    }
    func3(f3arg: string): Builder<T> {
        this.f3arg = f3arg;
        return this;
    }
}
const Builder: new <T>() => Builder0<T> = _Builder;

In this case, each method returns this, and at runtime it's just a "normal"-ish fluent interface. But the compiler will see new Builder<Person>() as something that produces not a Builder<Person>, but a Builder0<Person>, which (if you look at the type), is the same as a builder but it uses Omit to prevent TS code from accessing its func2 or func3 method. If you call func1(), it returns a Builder1<Person>, which prevents TS codefrom accessing its func1 or func3 methods. So when you call func1() followed by func2() followed by func3() the compiler takes a Builder0 and returns a Builder1 which returns a Builder2 which finally returns a Builder, which omits all those methods.

You can verify that it also enforces those order restrictions while maintaining the return value as the same class instance the whole time.


Hopefully one of those approaches gives you some ideas. Good luck!

Playground link to code

Upvotes: 2

lleon
lleon

Reputation: 2765

I don't know exactly what are you trying to achieve but here you go an example of a fluent API

export interface Person {
  firstName: string;
  lastName: string;
}

class Builder<T> {
  private func1Value?: string;
  private func2Value?: keyof T;
  private func3Value?: string;

  func1(arg: string): this {
    this.func1Value = arg;

    return this;
  }

  func2(key: keyof T): this {
    this.func2Value = key;

    return this;
  }

  func3(arg: string): this {
    this.func3Value = arg;

    return this;
  }
}

new Builder<Person>().func1('stringArg1').func2('firstName').func3('stringArg2');

Upvotes: 5

Related Questions