Fauna "Dash" for JS Driver

As a Typescript developer who loved RethinkDB, one criticism I have of Fauna at present is that the JS driver is a bit crufty to work with. I understand that it is modeled on FQL, but I’m reminded of how the original Rethink driver (which was a bit better to work with and reason about than Fauna JS driver imo) pretty quickly had a community written driver that was a lot simpler and easier to use. This seems like a product platform anti-pattern: ideally the official driver is also the best.

Fauna JS driver looks a bit to me like the product of the workshop of the gods - that’s to say, I appreciate the conceptual purity and that the underlying architecture is quite beautiful, but the tool could be refined and simplified for daily use by normal humans. After all, developers want the simplest experience possible, and do not want to have to fuss with the overhead of a particular db client syntax, or really any extra overhead in dealing with data ops.

Specifically here are some criticisms:

  • Having to unpack and/or reference a lot of function variables from the fauna.query, within a client.query call is overhead. These ought to be chainable methods that produce a promise.
  • So if the above were true, Lambda functions could simply be a “then”, which definitely simplifies the syntax. I think a lot of function vars feel like just too much syntax, and definitely not in to flow of scope. Example: Match(Index(“all_Pilots”)) or Lambda(‘pilotRef’, Get(Var(‘pilotRef’))). Many of the wrapper functions could be simpler to reason about if they were sugared up a bit, especially in chainable methods, and/or combined where it makes sense.
  • Having to my reference id in a wrapping object might be conceptually clear given the built in pagination and etc, but having to compose the id into my data is more work than I have to do with ie PG or Mongo - by default I just want my data. And there are multiple layers where Fauna gives me extra wrapping when I just want data.

So, to hopefully sound like a complainer as least as possible, here are some examples of how the driver could be improved for JS devs, based a lot on the RethinkDB driver, both the original and the “dash” community version. (And forgive if I don’t do Fauna justice here, but I’ve only begun to learn, and these are the issues I’m having.)

import { Client } from “faunadb”; // nice ES6 syntax (should be in examples btw)
const { Q } = new Client({ … }) // So we don’t mess with existing structure, “Q” is a sugared up object for making chained db queries - our “dash”

await Q.collection('Person')
  .create({
    _id:123,  // or an alpha-numeric id please :}
    occupationCode:'mech',
    name:'Bob'
  })
  // note that we get an _id that is the ref_id 
  // and the data without a wrapper from the "dash"
  // Why do I need a full ref object when 
  // I just referenced the collection myself?
  // I only need the doc _id.
  .then(({ _id, name })=>(...))
await Q.collection('Person').get(123)

the dash returns only the data:

{
  _id: 123,
  occupationCode:'mech',
  name: 'Bob'
}

compare to (something like) this:
(and actually I could not figure out how to coerce my ref id into the data output itself,
from the docs and examples, although I’m sure there’s a way - sorry, I’m only a day or so into Fauna -
but I do know it’s a bit complex.

client.query(
  Map(
    q.Get(q.Ref(q.Collection("Person"), "123")),
    ... // some complicated Lambda or Let or Select that I couldn't figure out
  )
)
  .then((res) => res.data)

A join:

Q.index("Person_by_occupation_code")
  .join(
    "Occupation", // join either a collection or index?
    // an anonymous function that gets each record set and allows us to set the join functionally
    (person, occupation) => person.occupationCode === occupation.code
  )
  // a "then" is the same as a lambda, without the awkward scoping or need for 
  // extra helper functions. 
  .then(
    ({ Person, Gender: { name } }) => ({
      ...Person,
      occupation: name,
    })
  )
// we might allow for a pagination method to override default:
.paginate({ ... })
;

So that’s any promise that queries a list produces a pagination by default - that’s a nice feature and I think acceptable to have a wrapping object in the “dash” result to help with pagination.

{
  data:[
    {
      _id: 123,
      gender:'m',
      name: 'Bob',
      occupation:'Mechanic'
    }
  ]
}

Compare to this (which does not include all the formatting required to get a merged data output with a ref_id in the data itself, which will add a ton more helper method calls in current Fauna driver):

fauna.query(
  q.Let(
    {
      Person: q.Get(q.Match(q.Index("Person_by_occupation_code"))),
      Occupation: q.Select(["data", "occupationCode"], q.Var("Person")),
    },
    q.Get(
      q.Match(q.Index("Person_by_occupation_code"), q.Var("code")) 
    )
  )
)
.then(res=>res.data)

The former is much more fluid within scope (more the JS way) and uses much fewer function methods.

I could go on, but hopefully this get the need across. I’m sure your engineers could do much better than this quick example, but hope it helps!

Thanks!

Hi @tehcromic,

Thanks very much for this comment. We recognize the same points of friction with the drivers, as well as the query syntax. We’re working to introduce improvements to the driver, among which includes a a fluent API with method chaining. It will be very similar to what your asking for here. I can’t give you an eta just yet, but the work is in progress.

Your thoughtful comment here is very much appreciated.