graphistry / falcor Goto Github PK
View Code? Open in Web Editor NEWGraphistry forks of the FalcorJS family of projects in a lerna-powered mono-repo.
Graphistry forks of the FalcorJS family of projects in a lerna-powered mono-repo.
Handling a path with the form of:
"foo.WHATEVER"{
call(pathSet, args) {
return [
{
"path": [
"bar",
"baz"
],
"value": 1
},
{
"path": [
"foo",
"zap",
0
],
"value": 2
}
]
}
}
The resullting jsonGraph will be:
{
foo: {
"bar":{
"baz":1 //WRONG
},
"zap":{
0:2
}
}
}
While on falcor-router the same response has the correct structure
{
"bar":{
"baz":1
foo: {
"zap":{
0:2
}
}
}
The first path is concatenated with the root (thisPath) of the call path, the remaining paths goes unchanged
This is not really an issue at all, so I can migrate elsewhere if you think there's a better place to discuss.
After trying out a bunch of different patterns for integrating Falcor into a React app (in my case backed by a Redux store, though that wasn't a hard requirement), I came up with the following naive approach, based in large part on some of the approaches you take in falcor-react-redux
/falcor-react-schema
.
// initialize model with change$ stream
const change$ = new BehaviorSubject();
const model = new Model({
source: new LocalDatasource(graph),
onChange: () => change$.next()
})
.batch()
.boxValues()
.treatErrorsAsValues();
// create connectFalcor HOC
const connectFalcor = propsToGraphStream => BaseComponent =>
hoistStatics(compose(
setDisplayName(wrapDisplayName(BaseComponent, 'Falcor')),
getContext({ falcor: PropTypes.object }),
mapPropsStream(props$ => props$
.merge(change$.withLatestFrom(
props$,
(_, props) => props
))
.switchMap(
({ falcor, ...props }) => propsToGraphStream(props, falcor),
({ falcor, ...props }, falcorResponse) => ({ ...props, falcorResponse })
)
.auditTime(0, animationFrame)
)
))(BaseComponent);
// connect an example todo component
const ConnectedTodoList = compose(
// optionally connect to Redux store, though props needed for falcor request
// could just as easily be passed as props to the connected HOC
connectRedux(state => ({
range: state.todos.range
})),
connectFalcor(({ range }, falcor) =>
Observable.from(falcor.get(
['todos', range, ['title', 'status', 'createdAt']],
['todos', 'length']
).progressively())
),
// optionally deserialize the falcor response
mapProps(({ range, falcorResponse: jsonEnvelope }) => ({
todosLength: jsonEnvelope.json.todos.length,
todos: jsonEnvelope.json.todos.slice(range.from, range.to).map(deserializeTodo)
}))
)(TodoList);
So far, everything works pretty well for the few use cases I've come across so far. Curious if you have any feedback, and whether or not this tracks at all with what you are trying to do w/ falcor-react-redux
/falcor-react-schema
.
There are some notable potential performance issues where the wrapped component will be updated unnecessarily:
version
on the falcorJSON
?Anyhow, thought I'd share. And thanks for open sourcing the work you've done, which all of the above is based on.
const model = new Model({
source: new DataSource(...),
recycleJSON: true
});
model.get(['search', 'EORJy', 'match', 'length'])
.subscribe({
next(res) { console.log('search result:', res); }
});
model.get(['thing', '1ZJya', 'defaultLabel'])
.subscribe({
next(res) { console.log('item result:', res); }
});
// > "search result: { json: { search: EORJy: { ... } } }
// > "item result: { json: { search: EORJy: { ... } } }
The model behaves as expected when recycleJSON: true
is removed. Also, this does not appear to occur when I change model.get
to model.getValue
, or make the second get request after the first has resolved.
(continues discussion at end of issue thread #8)
So, the use case is iterating a FalcorJSON
response instance while avoiding metadata. As @trxcllnt points out, b/c the FalcorJSON
class copies methods from the Array.prototype
, you can use map/reduce/filter/etc. assuming the FalcorJSON
instance has a numeric length property.
However, this doesn't work if using boxValues
to make requests, as length resolves to an Object (e.g. { $size: 51, $type: "atom", value: 1221 }
).
I was considering overwriting the FalcorJSON
methods to use the length boxedValue at length.value
, using a polyfill as a guide , but realized that for cases where I was working with graph fragments with large indices, (e.g. ['todos', { from: 1000, to: 1010 }, ...]
), there would be unnecessary overhead.
So, instead I wrote a pretty naive utility function
const mapFalcorJSON2Array = (project, from, to, falcorJSON) => {
const result = [];
while (from <= to) {
result.push(project(falcorJSON[from], from));
from += 1;
}
return result;
};
// e.g.
mapFalcorJSON2Array((todo, idx) => ({ ...todo, idx }), 1000, 1010, todosFalcorJSON)
Not sure if there are better/more general solutions. @trxcllnt thoughts.
The main
property field of the falcor/package.json
should point to the dist/falcor.js
file instead of the dist/falcor.min.js
field.
getValue is not parsing a path here https://github.com/graphistry/falcor/blob/master/packages/falcor/lib/get/getValue.js#L5
While on Netflix falcor it accepts an unparsed path https://github.com/Netflix/falcor/blob/master/lib/get/getValue.js#L5.
Was this an intentional api change?
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?
const model = new Model({
source: {
get: () => Observable.timer(100)
.concat(Observable.throw({
status: 500
}))
},
recycleJSON
})
model.get(['items', 0, 'title'])
.subscribe({
error(err) {
console.log('Error', err)
}
});
If recycleJSON is true
, logs:
// > { $type: 'error', value: { status: 500 } }
If recycleJSON is false
, logs:
// > [{ "path": ["items", 0, "title"], "value": { "status":500 } }]
I saw that doing a model.get() returns a json object with f_meta as a key.
Is there a way to get a pure object like plain netflix falcor ?
Thanks
In cases like this:
const ComponentWithFalcor = connect(SomeComponent);
render((
<ComponentWithFalcor someProp="value" />
), document.getElementById('app'));
the inner SomeComponent
won't receive the prop someProp
. It looks like this is b/c mergeReduxProps
(here) only passes falcor
from the third ownProps
argument.
Unless I'm missing something, mergeProps isn't necessary here. I tried changing to
connectRedux(mapReduxStoreToProps, null, null, reduxOptions),
and it seemed to work. Haven't tested properly, though, b/c I haven't gotten the lerna build working...
For context, this is in relation to this ticket. Requests that return 400/500 populate the graph w/ error sentinels, and subsequent retries simply hit the cache, rather than actually making the API call again. Being able to set expiry metadata on sentinels returned from the datasource would solve this. But it looks like metadata is not actually applied to nodes in the cache (though if I'm reading this correctly, it looks like it should...).
E.g.
observer.onError({
$type: 'error',
value: {
message: errorMessage,
status
},
$expires: 1
});
I still need to confirm whether or not this is happening in the original Netflix implementation.
Is the FalcorJSON.$__status
property supposed to reliably determine whether or not a model query is done emitting?
I'm running into the following issue:
model cache is populated with some results, e.g. [search, 1, { to: 9 }, title]
query model that emits progressively for paths, some of which can resolve in the local cache
model.get(['search', 1, { to: 19 }, 'title']).progressively()
.subscribe({
next(falcorJSON => {
console.log('response:', falcorJSON.json.search[1]);
console.log('status:', falcorJSON.json.$__status);
})
});
resolved
status// > response: { 0: { title: '...' }, ..., 9: { title: '...' } }
// > status: resolved
// sometime later...
// > response: { 0: { title: '...' }, ..., 19: { title: '...' } }
// > status: resolved
Is this a correct usage of $__status
?
@trxcllnt getting the following error when building with webpack:
ERROR in ./~/@graphistry/falcor/dist/falcor.min.js
Module not found: Error: Cannot resolve module 'promise' in node_modules/@graphistry/falcor/dist
@ ./~/@graphistry/falcor/dist/falcor.min.js 4:288-306
I can work around it by installing the promise module in my devDependencies
, but you probably have a dangling require
somewhere that is making webpack
try to load it just in case.
@trxcllnt as discovered earlier today -- should guard against setting the _node property on the model if the function is given a path (happened when using #getVersion(path)).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.