orbitjs/orbit

[JSONAPISource] First-class support for sparse fieldsets

bradjones1 opened this issue · 7 comments

It seems currently that the only way to request sparse fieldsets when querying a JSON:API source is to set sources.[jsonapi-source].remote.settings.params in the query options, and build the query parameter manually (including repeating the resource name).

This would be similar to how include and pagination are handled at

export function buildFetchSettings(
options: JSONAPIRequestOptions = {},
customSettings?: FetchSettings
): FetchSettings {
let settings = options.settings ? clone(options.settings) : {};
if (customSettings) {
deepMerge(settings, customSettings);
}
['filter', 'include', 'page', 'sort'].forEach((param) => {
let value = (options as any)[param];
if (value) {
if (param === 'include' && Array.isArray(value)) {
value = value.join(',');
}
deepSet(settings, ['params', param], value);
}
});
return settings;
}

Refs #553

dgeb commented

I agree. I'll try to land first-class support for sparse fieldsets soon.

Thanks. As I continue to work on my project where I'm leveraging Orbit, I have this use case:

  • When logging in, I want to/need to fetch some basic information about the user, but not all fields.
  • When that same user edits his profile, I need to fetch more information, including fields I would not otherwise load.
  • I need to suss this out, but I'm not entirely sure what the current behavior is regarding (re-) fetching the resource. Do the additional attributes get merged in, e.g. if I'm also using a memory source?

Or put another way, I think the goal is to avoid having to set the request URL on any subsequent requests. I discover the user by calling a collection, /me, which returns the user; I've now got the ID (and the resource in memory) so it would be ideal to deterministically know what happens with various combinations of sparse fieldsets, similar to doing a transform to update a specific attribute.

dgeb commented

Do the additional attributes get merged in, e.g. if I'm also using a memory source?

Yes, fields will be merged with existing fields, only replacing those that are included in the new version of the resource. This is true for updateRecord operations as well as more fine-grained operations such as replaceAttribute, replaceRelatedRecord, or replaceRelatedRecords.

Or put another way, I think the goal is to avoid having to set the request URL on any subsequent requests.

This is not yet possible with the base orbit lib. But it's an easy addition that could fit well with either a model layer or utility function. Just access the record's links.self and use that to form a request with a url option.

Thanks for the clarification. I think I was a bit unclear on the second point; by "set the request URL" I meant, not having to set the query parameters directly inside request options, a la:

    return memory.query(q => q.findRecords('user'), {
      fullResponse: true,
      sources: {
        remote: {
          url: '/me',
          settings: {
            params: {
              'fields[user]': 'push_token',
            },
          },
        },
      }
    }).then(data => ...

But I think that's basically "the point" of the feature; since you clarified that fields are merged into the memory store, you can do follow-on sparse fieldset requests. Thinking more about how this might work, I think the next question/issue to address would be, how to handle requests for sparse fieldsets that do not or partially intersect with the memory cache's current state. That is, what if I already have A and B fields in memory but do a sparse fieldset query on A, C and D?

Sorry if that's a bit simple or pedantic; I'm learning a lot of JS as a I go and don't purport to understand all of the existing internals.

Thanks as always.

dgeb commented

I think the next question/issue to address would be, how to handle requests for sparse fieldsets that do not or partially intersect with the memory cache's current state. That is, what if I already have A and B fields in memory but do a sparse fieldset query on A, C and D?

Orbit would handle this scenario by merging the current value of B with the new values for A, C, and D.

I'm working to improve the ergonomics of these sparse fieldset requests.

dgeb commented

#911 adds first-class support for sparse fieldsets to the jsonapi package. Your request above could now be made by passing a mapping of types to fields, such as:

let users = await memory.query(q => q.findRecords('user'), {
  fullResponse: true,
  sources: {
    remote: {
      url: '/me',
      fields: {
        user: ['pushToken']
      },
    },
  }
});

Note that the fields option above can use the ResourceFieldParam serializer (if you have one defined) to translate pushToken to push_token so you no longer need to perform this translation.