Retrieving Collection Instance by Name in FaunaDB

Hello Fauna Community,

I am currently trying to retrieve a collection instance by its name, but I am encountering some difficulties.

According to the documentation, the Collection.byName method returns a CollectionDef (or NullCollectionDef) object. These objects represent the collection document rather than an instance of the collection itself.

Here’s the issue: I need to perform operations such as byId , update , and create on the collection instance. However, these methods do not exist on the CollectionDef objects returned by Collection.byName . Here’s a snippet of the code I’m working with:

export const upsert = async <T extends QueryValue>(collection: string, id: number, data: T) => {
  const exp = fql`
    let Collection = Collection.byName(${collection})

    if(Collection.byId(${id}).exists()) {
      Collection.update(${id}, ${data})
    } else {
      Collection.create(${data})
    }
  `

  return await client.query(exp)
}

In this context, I’m trying to create an upsert function that either updates an existing document in a collection or creates a new one if it doesn’t exist. The collection name, document ID, and data are provided as parameters. I would greatly appreciate any guidance on how to retrieve a collection instance by its name, or any alternative approaches to achieve the desired functionality.

I see what you’re doing, but it seems to be inspired by ORMs from libraries like Ruby on Rails or Laravel. I would advise that approach won’t work here.

Instead, you’ll need to work more directly with the faunadb api to call the right methods.

Hi @zvictor! This is a totally valid use case. The drivers are intended to be lower level and the most flexible for everyone to build on top of.

You can create an instance of the Collection, as opposed to a CollectionDef (docs), by creating a Module object in javascript and using that as your dynamic argument.

export const upsert = async <T extends QueryValue>(collectionName: string, id: string, data: T) => {
  const mod = new Module(collectionName)
  const dataWithId = { ...data, id }
  const exp = fql`
    let coll = ${mod}
    let doc = coll.byId(${id})
    if(doc.exists()) {
      doc.update(${data})
    } else {
      coll.create(${dataWithId})
    }
  `

  return await client.query(exp)
}

Collection.byName is analogous to something like Users.byId. They both return the underlying Document. Everything persisted in Fauna is just another Document. Documents have specific methods, and you will find that a CollectionDef has the same methods. We need to be able to separate methods like updating a Document within a Collection from updating the Collection definition itself.

1 Like

To clarify the “Module” bit: it’s really only present when working with drivers, or otherwise handling network requests. That is, things like Collections, Functions, Roles, etc. come back over the wire typed as a Module; they are encoding literal FQL identifiers.

You can see how we use Module in the Javascript driver, for a bit more of a deep dive: https://github.com/fauna/fauna-js/blob/74804f9f10e9651200e3cb696e43dabd90a6c11f/src/values/doc.ts#L190

Beautiful! I might even consider releasing a v2 of brainyduck once I understand better such inner workings :blush:

Anyway, this is the generic concat method I managed to come up with:

export const concat = async <T extends Array<QueryValue>>(
  collectionName: string,
  id: string,
  field: string,
  data: T
) => {
  const exp = fql`
    let Collection = ${new Module(collectionName)}
    let doc = Collection.byId(${id})

    let content = (doc[${field}] ?? []).concat(${data})

    let update = Object.fromEntries([[${field}, content]])
    doc.update(update)
  `

  return await client.query<Page<T>>(exp)
}

2 questions:

  • Is there any other way I could achieve the same result without using Object.fromEntries? I struggled a bit trying things like doc.update({ [${field}]: content }) and let obj = { ${field}: content }.
  • The type Page is misplaced. What other types can a client.query return?

The spread operator and dynamic keys, (a la JS, e.g. { ["key"]: value}) or on the list for potential improvements. For now, though, you need to use Object.fromEntries to provide dynamic object keys.

The Client.query method returns a QueryValue type. It is essentially, any JS value plus the classes provided by the driver, but no user-defined classes.

If you are looking for a deeper dive into the inner working, definitely check out this source file:

1 Like

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