"Upsert" FQL example not working

I’m unable to get this “Upsert” recipe to work. I keep getting an “instance already exists” error with the following implementation:

import { query as q } from 'faunadb'
import faunadbClient from '/components/api/faunadb/client'

export default async(req, res) => {
  try {
    await faunadbClient.query(
      q.CreateFunction({
        name: 'upsert',
        body: q.Query(
          q.Lambda(
            ['ref', 'data'],
            q.If(
              q.Exists(q.Var('ref')),
              q.Update(q.Var('ref'), q.Var('data')),
              q.Create(q.Var('ref'), q.Var('data'))
            )
          )
        )
      })
    )
    await faunadbClient.query(
      q.Call(
        q.Function('upsert'),
        q.Ref(q.Collection(req.query.collection), req.query.id),
        {
          data: req.body
        }
      )
    )
    res.status(200).end()
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
}

However, if I run the same query on a Update specific endpoint, the document values are updated fine without error, like so:

import { query as q } from 'faunadb'
import faunadbClient from '/components/api/faunadb/client'

export default async(req, res) => {
  try {
    await faunadbClient.query(
      q.Update(
        q.Ref(q.Collection(req.query.collection), req.query.id),
        {
          data: req.body,
        }
      )
    )
    res.status(200).end()
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
}

Any ideas on what is going wrong here?

Sorry, I just realised the problem. I’m trying to create the ‘upsert’ function when it already exists. This can be ignored/closed/deleted. Sorry for the unnecessary noise!

If anyone is interested or can recommend a better approach, this is how I’m creating functions only if they don’t already exist:

import { query as q } from 'faunadb'
import faunaDBClient from '/components/api/faunadb/client'

export default async(name, body) => {
  try {
    let customFunctions = []
    let exists = false
    await faunaDBClient.query(
      q.Paginate(q.Functions())
    ).then((ret) => {
      customFunctions = ret.data
    })
    customFunctions.forEach(customFunction => {
      exists = customFunction['@ref'].id === name ? true : exists
    })
    if (!exists) {
      await faunaDBClient.query(
        q.CreateFunction({ name, body })
      )
    }
    return true
  } catch (e) {
    return e
  }
}

Which can be used to create the ‘upsert’ function like so:

await FaunaDBCreateFunction('upsert', q.Query(
      q.Lambda(
        ['ref', 'data'],
        q.If(
          q.Exists(q.Var('ref')),
          q.Update(q.Var('ref'), q.Var('data')),
          q.Create(q.Var('ref'), q.Var('data'))
        )
      )
))

Functions are Documents, too! So similar to how you can “upsert” a Document, you can upsert a Function.

If you’re using JS, you can create a CreateOrUpdateFunction helper

const CreateOrUpdateFunction = (params) => q.Let(
  { 
    name: Select("name", params),
    params
  },
  q.If(
    q.Exists(q.Function(q.Var("name"))),
    q.Update(q.Function(q.Var("name")), q.Var("params")),
    q.CreateFunction(q.Var("params"))
  ),
)

The Let here helps to reduce how big the final query is. Otherwise, we would duplicate the entire set of params for both the create case as well as the update case.

Use it as a replacement for CreateFunction

    await faunadbClient.query(
      CreateOrUpdateFunction({
        name: 'upsert',
        body: q.Query(
          q.Lambda(
            ['ref', 'data'],
            q.If(
              q.Exists(q.Var('ref')),
              q.Update(q.Var('ref'), q.Var('data')),
              q.Create(q.Var('ref'), q.Var('data'))
            )
          )
        )
      })
    )

You can create similar helpers for CreateOrUpdateDatabase, CreateOrUpdateCollection and CreateOrUpdateRoles. You can do so with Indexes as well, but it would have to have extra validation, since you cannot edit terms or values once an Index is created.

2 Likes

:tada:

This is great, thanks!

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.