Derive locally cached state based off a query/cache updates

I have a query that pulls in a Model. Inside this model, there are nested models with fields.

The shape is roughly like this:

{
  model: [
    {
      id: 1,
      fields: [...]
    },
    {
      id: 2,
      fields: [...]
    }
  ]
}

Additionally, the frontend needs the model normalized into a list, like this:

{
  modelFields: [
    {...},
    {...},
    {...},
    {...}
  ]
}

I’m attempting to derive modelFields declaratively when a query or cache update changes model. I’m trying to achieve this in type-policies section on Model: { merge: modelMergeMiddleware }, like so:

export function modelMergeMiddleware(
    __: ModelFragment,
    incoming: ModelFragment,
    {cache, readField}: FieldFunctionOptions
) {
    if (incoming) {
        cache.writeQuery({
            query: ModelFieldsDocument,
            data: {
                modelFields: incoming.fieldsets.reduce(
                    (fields: ModelFieldFragment[], fieldset: FieldsetFragment) => {
                        return fields.concat(newFields)
                    },
                    []
                )
            }
        })
    }
    return incoming
}

However, this runs into problems:

  • nested cache references don’t get passed through leaving empty data
  • readField and lodash’s _.cloneDeep both result in Readonly data that cause errors

My question is two-fold:

  1. Is there a method to work around the problems mentioned above to derive data in a merge function?
  2. Is there a different approach where I can declaratively derive local-only state and keep it synchronized with cached objects?

Per question 2, my backup approach is to use a reactiveVar/Recoil to store this data. This approach has the tradeoff of needing to call a setter function in all the places the Model object gets queried or mutated in the app. Not the end of the world, but it’s easy to miss a spot or forget about the setter function.