Create a UDF that contains JavaScript functions

I know that simple UDFs can be created in the Fauna dashboard, but how do I execute this FQL, which contains a JavaScript function in the blog Refreshing authentication tokens in FQL? :

export default CreateFunction({
  name: ‘login’,
  body: Query(Lambda([‘email’, ‘password’],
    LoginAccount(Var(‘email’), Var(‘password’))
  )),
  role: ‘server’
})

That’s not FQL. There is FQL included, but that is a JavaScript function. You need all of the dependencies to be loaded in order to call it.

The JavaScript driver implements FQL functions that build query expressions. This means that you can use JavaScript loops, iterators, and other features to programmatically compose large queries. The query itself is not serialized and sent to Fauna until you call client.query(<query>). While that is a powerful feature, it can sometimes be harder to distinguish between the JavaScript and FQL parts.

Per the blog post, this function is defined here: fauna-blueprints/login.js at main · fauna-labs/fauna-blueprints · GitHub

You can see that this file imports the LoginAccount function from here: fauna-blueprints/login.js at main · fauna-labs/fauna-blueprints · GitHub

LoginAccount itself calls other JavaScript functions that compose parts of the FQL query. These functions create very small portions of the overall FQL query, and while that level of granularity does require some effort to understand the overall query, it does provide a lot of flexibility to compose new queries without having to repeat similar (if not identical) query expressions.

That’s the power of a highly-composable, functional language: you can use logic to compose logic to achieve your goals.

Fully unrolled, the composed query would look something like this:

CreateFunction({
  name: 'login',
  body: Query(
    Lambda(
      ['email', 'password'],
      If(
        And(
          Exists(Match(Index('accounts_by_email'), Var('email'))),
          Identify(
            Select(
              ['ref'],
              Get(Match(Index('accounts_by_email'), Var('email')))
            ),
            Var('password')
          )
        ),
        Let(
          {
            account: Get(Match(Index('accounts_by_email'), Var('email'))),
            accountRef: Select(['ref'], Var('account')),
            tokens: Let(
              {
                refresh: Create(
                  Tokens(),
                  {
                    instance: Var('accountRef'),
                    data: { type: 'refresh' },
                    ttl: TimeAdd(Now(), 604800, 'seconds'),
                  }
                ),
                access: Create(
                  Tokens(),
                  {
                    instance: Var('accountRef'),
                    data: { type: 'access' },
                    ttl: TimeAdd(Now(), 600, 'seconds'),
                  }
                )
              },
              {
                refresh: Var('refresh'),
                access: Var('access')
              }
            ),
          },
          {
            tokens: Var('tokens'),
            account: Var('account')
          }
        ),
        false
      )
    )
  ),
  role: 'server'
})

That query should work to define the login function; you should be able to paste it into the Dashboard Shell (or fauna-shell). Before you try to call the function, you’d have to make sure that the accounts_by_email index was created. Then you can call it with:

Call('login', '<username>', '<password>')
1 Like

Hi Koji,

LoginAccount in that snippet is not a Fauna function; it’s a JavaScript function within a JavaScript source file that gets evaluated into a series of FQL functions before it’s run on Fauna itself. If you look at the source for the JavaScript it’ll make more sense:

const q = faunadb.query
const { Let, Var, Select, Match, Index, If, Get, Identify, Exists, And } = q

export function LoginAccount (email, password, accessTtlSeconds, refreshTtlSeconds) {
  return If(
    // First check whether the account exists and the account can be identified with the email/password
    And(
      VerifyAccountExists(email),
      IdentifyAccount(email, password)
    ),
    CreateTokensForAccount(email, password, accessTtlSeconds, refreshTtlSeconds),
    // if not, return false
    false
  )
}

So first, JavaScript creates a const that instantiates faunadb.query. Then it maps a series of FQL functions on top of the original const, q. So when LoginAccount() is called, it returns the resulting FQL to the driver, which then passes that FQL to Fauna to be run there.

LoginAccount() couldn’t ever be run in the Fauna shell directly this way; you would have to run it within a JavaScript program.

Please let us know if this answers your question or if there’s anything else we can help with on it.

Cory

1 Like

Thanks @ewan @Cory_Fauna. Both the method of pasting the fully expanded query into the Fauna Shell and executing it, and the method of executing LoginAccount () within my JavaScript program worked.

2 Likes