Most of the time, I do just the simple const { query: q } = require('faunadb')
and use q
throughout.
What I do after that has been dependent on the environment.
Helper functions
pretty much no matter what, I am leaning on helper functions in JS that help compose more complex queries. Lots of helpers like UpdateOrCreateDoc
, UpdateOrCreateFunction
, ExistsOrAbort
, PaginateWithOptions
.
A lib like faunadb-fql-lib
fills in some of the blanks with FQL, but what I mean is wrapping more significant things into JS functions.
Bootstrap scripts
Most of the FQL I right is built in to start up scripts. Step 1, upload GraphQL Schema if desired; Step 2, run a great GREAT many bootstrap queries. My opinion is that any non-trivial queries should be UDFs.
What I have been trying to do is find a most convenient way of maintaining all of the bootstrap code, breaking down into smaller and readable chunks, separating the FQL from the error handling/debuggins (console logging).
I’ve taken to making a lists of “actions” exported from different files.
actions: [
{
description: 'Function: CreateThingForUser`,
query: 'createOrUpdateFunction({... functionParams})
},
...
]
From there, can import them from many different files, map over and execute them.
Lambda
vs. arrow function
The JS driver does not accept an arrow function with 0 arguments as a replacement for Lambda
. This makes it not possible to be consistent with using it everywhere.
Because of this I avoid using arrow functions directly in FQL. I will create helpers and use those directly, but you shouldn’t see them directly inside Map
, CreateFunction
, etc.
// NOT okay
q.Map(set, (ref) => q.Get(ref)))
// Okay
q.Map(set, q.Lambda(['ref'], q.Get(q.Var('ref')))
// -or-
const getRef = (ref) => q.Get(ref)
q.Map(set, getRef)
Client queries and serverless functions
I’ve not tried to be fancy here in production code yet, though I’ve experimented with a few things. Like I said, I’ve stuck with using q.FQL_FUNCTION
everywhere.
Now that I’ve uploaded pretty much everything to UDFs, though, all queries are pretty small:
const result = await client.query(
q.Call(q.Function('AllMyThings'), 64, null, null)
)
Only longer if I need to map, but only so much due to helpers
// helper
const getRef = (ref) => q.Get(ref)
// query
const result = await client.query(
q.Map(q.Call(q.Function('AllMyThings'), 64, null, null), getRef)
)
Fancy things I’ve tried
Lens library
Forget looking at any FQL! Replace all common view and update operations with lenses, a la shades
const result = await client.query(
get(documents(), all(), getRef(), 'data', 'name')(q.Collection('User'))
)
(Ok, still have to use some FQL directly)
Having a “lens” that can work on both a query and the returned data was neat to work with.
This was fun, but I have not been so impressed that I am too drawn to keep working on it. I’ve concluded that when I want FQL, it just ends up so much more complicated than that!
Hardcore functional
Creating a library that works with fp-ts
and io-ts
along with Fauna has led to some interesting coding options, including the following contrived snippet.
A decoder can ensure that the query result matches the expected type, and you can feel confident about working with the results.
pipe(
TE.taskEither.of<AppErrors, NodeJS.ProcessEnv>(process.env),
TE.chain(validateEnv),
TE.chain(flow(createClient, createNewUser))
)().then(
flow(
E.chain(decodeUserDocument),
E.fold(
(e) => console.error(e),
(user) => {
console.log('id', user.ref.id)
console.log('id', user.ref.collection.id)
console.log('id', user.data)
}
)
)
)