graphistry / falcor

Graphistry forks of the FalcorJS family of projects in a lerna-powered mono-repo.
23 stars 3 forks source link

access absolute/resolved/dereferenced path #8

Closed jameslaneconkling closed 7 years ago

jameslaneconkling commented 7 years ago

Curious to know if you have a public api to access a node's absolute path (aka it's resolved/dereferenced path). I was surprised Netflix didn't expose this easily, except through acrobatics like

model.deref(["users", 0], "name").subscribe(function(userModel){
  console.log(userModel.getPath());
  // > ["usersById", 32]
});

// or

model.boxValue().get(['users', 0]).subscribe((res) => {
  console.log(res.json)
  // > { $type: 'ref', value: ["usersById", 32] }
});

I noticed that you stored path metadata under the JSONGraph's private f_meta.abs_path property. Is there any way to access this property?

trxcllnt commented 7 years ago

@jameslaneconkling sorry for the delay, I didn't have notifications turned on for the repo.

Yes, you can access the deref'd path from the output JSON via the $__path key. So in your example, you can get it from userModel.$__path.

I've been going back and forth on whether to move the $ props to a public namespace or not. So far, I've only had to use them in library-internals, so I've kept them $. Thoughts?

jameslaneconkling commented 7 years ago

@trxcllnt ah excellent, $__path is exactly what I was looking for. Specifically, if I make a get or call request with refs in the response, I can get the absolute paths for those nodes via something like (in my case) falcorJSON.json.search.<searchId>.matches.<searchResultIdx>.$__path.

Just thinking as I go here, but I'd guess there are at least as many good reasons for keeping this out of the public namespace, as there are for exposing it. The more common case for an end user, I'd guess, would be navigating through/iterating over the result data and ignoring metadata like $__path. But for cases where you are interested in the absolute/dereferenced path, $__path works, and b/c it's not enumerable, a user doesn't have to worry about accidentally iterating over it.

I vaguely remember a conversation on the Netflix falcor issue tracker about accessing or filtering out graph metadata, so that if you're iterating through your falcorJSON response, you don't have to worry about manually avoiding node metadata.

On a related note, are there available ways to iterate over a falcorJSON result set? B/c it's not a POJO, utils like R.map don't work, and Object.keys(falcorJSON) returns the f_meta key. It appears it does inherit methods from the Array prototype (from here?), but they don't work as expected, e.g. falcorJSON.map(x => x) always returns an empty array.

trxcllnt commented 7 years ago

I vaguely remember a conversation on the Netflix falcor issue tracker about accessing or filtering out graph metadata, so that if you're iterating through your falcorJSON response, you don't have to worry about manually avoiding node metadata.

Yeah, there aren't a lot of good options here. Falcor needs the JSON to have some metadata to know how to deref new Models, but we also don't want to pollute the output namespace too much. In the new mutable cache walk, I can get away with creating a second prototype Object and sticking the metadata on there, because the sub-tree diff'ing we get via hashcodes makes get so fast in the 80% case, we have a ton of breathing room to do extra allocations.

Alternatively, I've added support for a new branchSelector function, which will be called with the FalcorJSON instance as each branch is created. The JSON argument will already have the metadata attached, so you can use it as the prototype for your own instances if you want. You can define it once when you create the root Model (via new Model({ branchSelector: (json) => /* do your thing */ }).

On a related note, are there available ways to iterate over a falcorJSON result set? B/c it's not a POJO, utils like R.map don't work, and Object.keys(falcorJSON) returns the f_meta key. It appears it does inherit methods from the Array prototype (from here?), but they don't work as expected, e.g. falcorJSON.map(x => x) always returns an empty array.

So this is my probably-too-clever solution to make it easier to work with lists. If you request a range of integers along with the length, we can use the native Array methods on the JSON result. This is especially handy when combined with the falcor-react-schema/withFragment higher-order component (still a work-in-progress port to separate the older falcor-react-redux/container HOC logic from Redux), which fetches a component's query and reduces over the resulting JSON until the query stabilizes. I haven't had time to properly document/blog about everything, but here's the general idea:

import { withFragment } from 'falcor-react-schema';

function MyList({ items = [] }) {
  return <ul>{ items.map((item) => <MyListItem {...item}>) }</ul>
}

MyList = withFragment({
  fragment: ({ items = [] }) => `{
    items: {
      length, [0...${items.length}]: {
        whatever, item, keys, you, want
      }
  }`
})(MyList);

export { MyList }
jameslaneconkling commented 7 years ago

Alright, super helpful--thanks.

If you request a range of integers along with the length, we can use the native Array methods on the JSON result

Ah, OK. I actually did have a length property on my collection, however w/ boxValues() enabled, so that length looks like {$type: "atom", value: 357, $size: 51} rather than 357, the map method does not work. (but I can confirm that when I disable boxValues, it does work as expected).

RE: falcor-react-schema I haven't seen that published anywhere. Is it public? I have been looking at falcor-react-redux, which looks like it's done a great job of abstracting over falcor and redux (a hard problem (for me, at least), given that falcor is asynchronous, and the redux store is synchronous). Reminds me of Relay a bit. Hadn't started running w/ it yet, as I'm still working on decoding it, but your above example gives a good hint at how it works. Any plans to publish docs?