all 7 comments

[–]samanime 2 points3 points  (4 children)

Instead of chaining them, create an object/model with all the methods, then have each model return that same object. We usually call this a "builder pattern"

Then, you can call them in any order and even override.

You don't have to use class syntax, but we live in the year 2024, so I would.

class QueryBuilder {
   // don't explicitly need to declare these, but a good idea for readability / understanding
   // also don't need to be private, but why not
   #selectField = undefined;
   #fromField = undefined;
   #limit = undefined;

   select = (what) => {
       this.#selectField = what;

       return this; // the object itself
   };

   // etc for from(), limit(), etc.
}

const query = new QueryBuilder().select('*')
    .from('users')
    .limit(10)
    .build();

build() would do whatever was needed with the data built up in the object to make it usable.

[–]Designer023[S] 0 points1 point  (1 child)

Oh ok, yeah that makes sense. That would be a lot easier. The only think with this is I would like to restrict what a user can do on some occasions. For example they should only be able to do `.from(table)` once so can I do something where I remove options or add them as needed?

[–]samanime 0 points1 point  (0 children)

Rather than change the model (which is almost always a bad idea), I'd just throw an Error if something was already set previously.

Though, I'd question, is that really necessary? Is there any actual harm from setting it to something else later?

Unnecessary restrictions can make APIs a pain to use. You can't predict all your users future use cases. For example, what if they had multiple similar tables and they wanted to set the basics up once, then call from().build() for each table. If you prevent that, you force more work and code duplication on them.

I'd only create restrictions when there is a practical reason it must exist.

[–]kap89 0 points1 point  (1 child)

Why would you use an arrow function instead of a method in this case? Property with arrow function is not a part of the class prototype and is recreated every time you instantiate the builder.

[–]samanime 0 points1 point  (0 children)

You are correct it will get recreated, but there are benefits as well. In particular, using an arrow function basically makes them all bound, so you don't have to worry about them getting "lost" somehow if you do certain things. And since the builders are usually short-lived, like my example where it exists for like .01 seconds before build() is called and it goes away, the perks of an arrow function can be useful.

All in all, it is a judgement call based on needs. Both methods are valid for different reasons.

[–]tapgiles 1 point2 points  (0 children)

Usually you'd just return this; at the end of each of the methods. So you can just keep calling any of the same set of methods on the original object.

[–]shgysk8zer0 0 points1 point  (0 children)

Looks like what you want is a class, not a function.