fluentifyjs

    1.1.1 • Public • Published

    Fluentify

    A tiny fluent interface decorator for JavaScript objects.

    Build Status

    Install 🔩

    npm install fluentifyjs
    

    Usage 📖

    In simple terms, Fluentify works by allowing you to chain an objects method calls e.g.

    const fluentify = require('fluentifyjs');
    
    const api = fluentify({
      foo() {},
      bar() {}
    });
    
    api
      .foo()
      .bar()
      .done();
    

    "Wait a minute, where did done come from?" - good question!

    done(err, ...results)

    done is a function attached to the object by Fluentify and is used to indicate the end of the chain. When each function in the chain is called, rather than being executed, it's added to an internal queue. When done is called, Fluentify will purge the queue invoking each function in the correct order, keeping track of any return values.

    Note - if a done property already exists on the object it will be overwritten

    Each result is passed to done as an explicit parameter as an array. For example, if foo and bar yield results your done call may look like:

    .done((err, foo, bar) => {
      console.log(foo[0]); // foo result
      console.log(bar[0]); // bar result
    });
    

    Tip💡 - in scenarios where you have lots of result you can make use of the spread operator (ES6 feature) to bundle up your results into an array, or alternatively you can use the arguments object.

    If an error occurs at any point during the chain, done is invoked immediately and any remaining function calls are discarded.

    Promise support

    Prefer a Promise over a callback? Simply omit the callback from your done call and Fluentify will return one e.g.

    const results = await api.foo().bar().done();
    

    "How does Fluentify know what return values to store for each call?" - another good question!

    callback(err, result)

    For each function in the chain Fluentify expects the final argument to be a callback. The callback works just like any other in JavaScript i.e. passes control back to the caller and yields any results or errors.

    For the most part, your callback is going to be the same for each function e.g. cb => cb(...). To avoid having to repeat this boilerplate code in every call, simply omit the callback parameter and Fluentify will inject one for you.

    Note - callback is required even for non-async code, this helps Fluentify retain order regardless of what type of code is being run

    Example - implicit callback

    const fluentify = require('fluentifyjs');
    
    const api = fluentify({
      foo(cb) {
        // do cool stuff
        cb(null, 'foo');
      },
      bar(data, cb) {
        // do more cool stuff
        cb(null, 'bar');
      }
    });
    
    api
      .foo()
      .bar('1234')
      .done((err, ...results) => {
        console.log(results[0][0]); // foo
        console.log(results[1][0]); // bar
      });
    

    Notice in the above example we didn't pass a callback to either foo or bar

    If you do provide a callback, it's important to note that an additional callback inside of this is required in order to pass context back to Fluentify.

    Example - explicit callback

    api
      .foo()
      .bar('1234', (err, result, cb) => cb(err, `foo${result}`))
      .done((err, ...results) => {
        console.log(results[0][0]); // foo
        console.log(results[1][0]); // foobar
      });
    

    By passing an explicit callback, you take control away from Fluentify as it can't be sure what's going on in your code, for example, you might make an asynchronous call and need to wait for a reply or just do some stuff synchronously. To remove any doubt, a callback is provided as a means of letting Fluentify know when your done so it can resume processing.

    Note - the additional callback is injected automatically by Fluentify

    Accessing results

    As discussed earlier, Fluentify will keep track of each result and pass them to done. However, what if we need access to a particular result earlier than that? With Fluentify, you have two options - FQL or the results function.

    Fluentify Query Language (FQL)

    Fluentify has it's own little query language that it uses to bind results to parameters on calls in the chain. An FQL query will always start with $<index>

    • $ - prefix used to denote the start of an FQL query
    • <index> - index of the call whose result we want to bind

    For example, let's imagine the first call in your chain fetched a user model which looked like:

    {
      id: '12345',
      name: 'Bobby Tables',
      email: 'bobby.tables@drop.com'
    }
    

    Then in the next call, you wanted access to this full result - here's how we'd do that using FQL:

    const fluentify = require('fluentifyjs');
    
    const api = fluentify({
      get(url, cb) { ... },
      foo(user, cb) { ... }
    });
    
    api
      .get('/users/12345')
      .foo('$1')
      .done(...);
    

    In the above example, Fluentify will bind the result of the get call to the first parameter in the foo call.

    FQL supports deep indexing into the object tree. For example, lets assume we only need access to the users email in the foo call, in that case the FQL would look like $1.email, simple!

    It also supports indexing into array properties by denoting the index of the item in the array as if it were a property e.g. $1.list.0.

    Note - there are no limitations on how deep you can query into the object tree, however, please bear in mind that there is work involved in parsing & traversing so performance may be impacted

    results(resultset, cb)

    results is a utility function attached to the object by Fluentify and can be used as a way of peeking into underlying result set at a specific point in the chain. It's handled just like any other call in the chain therefore it has a callback and can yield it's own result

    const fluentify = require('fluentifyjs');
    
    const api = fluentify({
      foo(cb) {
        return cb(null, 'foo');
      },
      bar(cb) {
        return cb(null, 'bar');
      }
    });
    
    api
      .foo()
      .bar()
      .results((arr, cb) => cb(null, `${arr[0]}${arr[1]}`))
      .done((err, foo, bar, foobar) => {
        console.log(foo[0]); // foo
        console.log(bar[0]); // bar
        console.log(foobar[0]); // foobar
      });
    

    And since it yields it's own result, it means it can be used in conjunction with FQL

    api
      .foo()
      .bar()
      .results((arr, cb) => cb(null, `${arr[0]}${arr[1]}`))
      .consolidate('$3')
      ...
    

    Note - first parameter of consolidate would be "foobar"

    Bugs 🐞

    As much as us developers hate to admit it, sometimes our code doesn't always work as intended 😞 If you happen to find a bug with Fluentify please raise an issue and I'll do my best to convince you it's by design!

    Contribute 🚧

    There are lots of things to like about Fluentify, however, there is always room for improvement. More than happy to accept PRs as long as the change is for the greater good and, of course, includes covering tests.

    License

    This project is released under the terms of the MIT license.

    Install

    npm i fluentifyjs

    DownloadsWeekly Downloads

    2

    Version

    1.1.1

    License

    MIT

    Last publish

    Collaborators

    • avatar