Using `walkjs` to fetch and attach image metadata in Sanity queries

6 replies
Last updated: Jun 7, 2024
Is there an easy way to get the blurhash from the image metadata without having to do anything in groq?
Ideally I'd like it to just always pull down so I don't have to edit every query
Jun 6, 2024, 4:56 PM
No, you’d need to explicitly query for it each time.
Jun 6, 2024, 6:19 PM
Hey User! Yes, in the query every time but my team has in the past used utilities that use
walkjs
to traverse the returned data, grab the image refs and re-query the extra metadata, and reattach them to the data to avoid super long queries, highly recommend these!
Jun 6, 2024, 7:47 PM
user D
Do you have a code example?
Jun 7, 2024, 8:31 AM
import { WalkBuilder, deepCopy } from "walkjs"


export const resolveLinks = async (inputData, maxDepth = 5) => {
  const store = new Map()

  const replaceNode = (node, id) => {
    const doc = store.get(id)
    if (["link"].includes(node.key)) {
      const values = {
        slug:
          doc._type === "home"
            ? { _type: "slug", current: "/" }
            : {
                _type: "slug",
                current:
                  "/" +
                  (doc.slug?.current
                    ? doc.slug.fullUrl || doc.slug.current
                    : `${doc._type}`),
              },
        label: node.parent?.val?.label,
        docType: doc._type,
      }

      const _key = node.val._key || node.parent.val._key || doc._key
      if (_key) {
        values._key = _key
      }
      Object.keys(node.parent.val).forEach(key => delete node.parent.val[key])
      Object.keys(values).forEach(key => {
        node.parent.val[key] = values[key]
      })
    } else {
      Object.keys(node.val).forEach(key => delete node.val[key])
      Object.keys(doc).forEach(key => {
        const value = doc[key]
        node.val[key] = typeof value === "object" ? deepCopy(value) : value
      })
    }
  }

  const iterate = async nodes => {
    const ids = new Map()

    new WalkBuilder()
      .withGlobalFilter(a => a.val && a.val._type === "reference")
      .withSimpleCallback(node => {
        const refId = node.val._ref
        if (typeof refId !== "string") {
          throw new TypeError("node.val is not set")
        }

        if (!refId.startsWith("image-")) {
          if (!store.has(refId)) {
            // unresolved, add it to the list
            ids.set(refId, node)
          } else {
            // already resolved, can be replaced immediately
            replaceNode(node, refId)
          }
        }
      })
      .walk(nodes)

    if (ids.size) {
      // fetch all references at once
      const documents = await sanityClient.fetch(
        `*[_id in [${[...ids.keys()].map(id => `'${id}'`).join(",")}]]{...}`
      )
      documents.forEach(element => {
        store.set(element._id, element)
      })

      // replace them
      ids.forEach((node, id) => {
        replaceNode(node, id)
      })

      if (!--maxDepth) {
        console.error("Sanity autoresolver max depth reached")
        return
      }

      // iterate threw newly fetched nodes
      await iterate(nodes)
    }
  }

  await iterate(inputData)
}
Jun 7, 2024, 8:32 AM
Awesome, thanks 🙏
Jun 7, 2024, 8:33 AM
To break it down:
1. Go thru the response of a query, find anything with _ref
2. Add to a list
3. Query eveything on the list
4. Attach returned reference data to object
Jun 7, 2024, 8:33 AM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?