tonyhb / tectonic Goto Github PK
View Code? Open in Web Editor NEWA declarative REST data loader for React and Redux. Docs @
Home Page: https://tonyhb.github.io/tectonic/
A declarative REST data loader for React and Redux. Docs @
Home Page: https://tonyhb.github.io/tectonic/
Hi,
Sounds like a very interesting lib especially for those who cannot use graphql at the moment. My question is, could this lib be split up into something like tectonic.core and techtonic.react so that we could utilize it in other redux env like ng2-redux revue, etc? Is this lib heavy dependent on react? I think you would find fans of this approach outside of react too :).
Seems the component wrapper and the react decorator are the only react dependencies. Not too bad :)
First of all, I think the concept is wonderful, keep up the great work.
While dealing with REST and Redux, I've also ended up writing abstractions on top of data fetching actions, to make everything more consistent, but didn't quite get to the component decorator yet :).
I would be really glad to swap my implementation with tectonic, I tried to go through docs to find some answers to my questions, but couldn't find them, so posting them here:
// GET http://some-url
{
"href": "http://some-other-url"
}
// GET http://some-other-url
{
"href": "http://some-yet-another-url"
}
// ...etc
I've solved it currently with ability to chain action creators, but they are defined explicitly.
Let's say I want to handle the network related action in my reducer, is there any way of doing this? I understand, that most of the times having abstraction like this will solve that issue, but in case of migration f.e. migrating some existing logic might be not very straightforward all at once.
Is there any way to normalize data somehow?
Thanks!
In order to get live feeds for data we should create a websocket component.
Questions:
If the websocket endpoint returns a live feed for use with subscriptions (#35) we should indicate that the source definition returns a feed:
manager.fromSocket([
{
meta: {
url: '/api/v0/eents',
// tbd...
},
returns: Event.feed(), // TBD: is this a feed for a single item or a list of items?
])
If you redefine a prop name (ie this prop is passed in from the parent and you use it as a key in @load) this should error; it will redefine a query when created, breaking lookups for query statuses which then breaks loading data.
Scenario:
I have a model/spec, that I use to create
an item. But, the structure of the object that is returned is different.
CreateUserSpec
-> create model with tectonic -> 201 created/200 ok -> response is User
object.
So, if I use the sourcedef, returnType
-> User.item()
, I get the:
baseResolver.js:41 There is no source definition which resolves the query Query(Model: userCreateSpec, Fields: *, Params: {}, Body: {"Name":"test-net4","Role":"view-only", ReturnType: )
They definitely should.
Ok this is a complex one so bear with me... I have a RecordBuilder
component which is rendered by a route with path 'some-models/:someModelId'. This component handles the creation of a record when someModelId
is new
and the update of a record when someModelId
is any other value. I've created a container that utilizes load
like this:
const selectId = get('match.params.adapterId');
const selectIsNewRecord = createSelector(
selectId,
eq('new'),
);
const container = compose(
load(props => {
if (selectIsNewRecord(props)) return {};
return { record: Adapter.getItem({ id: selectId(props) }) };
}),
mapProps(props => {
const id = selectId(props);
const isNewRecord = selectIsNewRecord(props);
const createModel = data => props.query(
{
model: Adapter,
body: data,
queryType: 'CREATE',
},
(err, result) => {
if (!err) {
props.replace(`/adapters/${result.id}`);
props.push(`/adapters/${result.id}/operations`);
}
},
);
const updateModel = data => props.query({
body: data,
model: Adapter,
modelId: id,
params: { id },
queryType: 'UPDATE',
});
return {
isNewRecord,
onSubmit: isNewRecord ? createModel : updateModel,
};
}),
spinnerWhileLoading(props => (
props.isNewRecord ? false : props.status.record.isPending()
)),
);
The key part of this is that load
function returns an empty object when we're dealing with a new record. Everything works as expected except when handling the callback after createModel
. I get the error Uncaught TypeError: Cannot set property 'params' of undefined
which points to this block of code
// Assign the props newQueries to this.queries; this is gonna retain
// query statuses for successful queries and not re-query them even if
// the cache is now invalid
Object.keys(newQueries).forEach(function (q) {
_this2.queries[q].params = newQueries[q].params;
});
It seems that when load is expecting a previous query to already be present but since I return an object previously it fails? Starting to dive into your codebase so let me know and I'm more than happy to help. Or if you have a better way to handle this case I'm all ears.
Here I am again. :P
I'm getting this error while loading my page:
TypeError: query.hash is not a function
at Cache.getQueryStatus (/home/andrey/Projects/Datarisk/app-frontend/node_modules/tectonic/transpiled/cache/index.js:409:49)
at /home/andrey/Projects/Datarisk/app-frontend/node_modules/tectonic/transpiled/manager/index.js:165:28
at Array.forEach (native)
at Manager.props (/home/andrey/Projects/Datarisk/app-frontend/node_modules/tectonic/transpiled/manager/index.js:162:28)
at PropInspector.computeDependencies (/home/andrey/Projects/Datarisk/app-frontend/node_modules/tectonic/transpiled/decorator/propInspector.js:80:37)
at new TectonicComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/tectonic/transpiled/decorator/index.js:102:43)
at /home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:295:18
at measureLifeCyclePerf (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:294:16)
at ReactCompositeComponentWrapper._constructComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:280:21)
at ReactCompositeComponentWrapper.mountComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:188:21)
at Object.mountComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactReconciler.js:46:35)
at ReactCompositeComponentWrapper.performInitialMount (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
at ReactCompositeComponentWrapper.mountComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:258:21)
at Object.mountComponent (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactReconciler.js:46:35)
at ReactCompositeComponentWrapper.performInitialMount (/home/andrey/Projects/Datarisk/app-frontend/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
Did I miss something?
IE:
sourceDef.driverFunc({
query,
sourceDef,
success,
fail
});
Where:
IE:
// driver.js
const apiDriver = ({ query, sourceDef, success, error }) => {
/// do driver shit
sourceDef.meta.apiCall().then( response => {
success(response.body);
});
}
// resolver
class Resolver {
success:(query, sourceDef, data) => {
}
resolve: () {
// somewhere query and sourceDef are defined
let query, sourceDef;
const driverSuccess = (data) => {
return this.success({ query, sourceDef, data });
}
}
}
Example
import { Model } from 'tectonic';
class SomethingModel extends Model {
static modelName = 'something';
static fields = {
description: undefined,
id: undefined,
name: undefined,
};
}
export default SomethingModel;
results in Error: Must supply an ID field for this model
. The docs provide an example with id: undefined
. Is this valid or no?
We have a component requests data. The request fails. We make sure we don't re-request this data in an infinite loop when the status is updated by not rerequesting failed GET queries.
How do we know when to retry failed GET queries?
@load({
user: User.getItem({ id: 3 })
})
...
This lets us define global model transforms which are used before storing data, necessary when creating a key based on model data.
Each source definition should have a defaultParams
object. The base resolver should inspect the query params and add defaults before passing to the driver.
We need to know whether a source returns a list or an item.
If the query asks for a list and the source returns only an item the source can't fulfil the query.
Typically this is because people put <Loader> inside their <Router>
- it should be above. Throw an error and ask that question in console.
We're looking for a way to support sideloaded data that our API returns. So far we've made a custom driver that allows us to pass params to our API like /posts?include[]=creators.
. That in turn returns a payload in the shape of:
{
posts: [{
title: …,
creator: 111,
…
}],
users: [{
id: 111,
…
}]
}
Our models associate post.creator
with users. And we have a custom transform in our sources that returns response.body.posts
. So two questions:
Ideally a subsequent query to users like users.getItem({ id: 111 }),
would return the cached result from the original response.
One idea we're considering... perhaps could we dispatch the sideloaded data into the store with a query key that matches what we know a subsequent query would be looking for? If so, where in tectonic would we put that logic?
Essentially use graphQL to generate queries which are fed to our resolver; the resolver then calls REST API endpoints.
Here's an example of the result of a search call:
[
{
type: 'post',
data: {
id: 1,
title: 'some post'
}
},
{
type: 'user',
data: {
id: 5,
title: 'Foo McBar'
}
}
]
We might define the search endpoint as such:
{
params: ['query'],
returns: {
posts: Post.list(),
users: User.list()
}
}
How do we keep the ordering of the API results across many models?
Tony, comparing the startup guide in the site and the README, it seems to me that README is very outdated. For example, in README me nothing is mentioned about registering the tectonic in the reducers.
Which of the guides I can follow?
q: I deleted an item but it's still in my list
a: the model ID was not passed as a parmater to the delete function, therefore we could not figure out which model to remove from the reducer state
Instead of:
Model({
field: 'initialValue'
});
Model({
name: 'User',
fields: {
}
});
Currently using in a 15.x.x project and receiving Warning: You are manually calling a React.PropTypes validation...
on components wrapper with the load
decorator. Took a brief look into the src but couldn't see where this is happening. Probably worth upgrading React to the latest when fixing this issue.
Increased filesizes of frontend distributions suck, and yeah tree shaking is legit and maybe you won't end up bundling all of immutable.js, but why not include an option to go immutable free?
I have a very basic setup:
models/User.js
import { Model } from 'tectonic';
export default class User extends Model {
static modelName = 'user';
static fields = {
id: undefined,
name: '',
email: '',
position: '',
phone: '',
password: '',
createdAt: 0,
updatedAt: 0,
}
}
export const endpoints = [
{
returns: User.getList(),
meta: {
url: '/api/users',
},
},
];
setupManager.js
import { Manager, BaseResolver } from 'tectonic';
import TectonicSuperagent from 'tectonic-superagent';
import { endpoints as userEndpoints } from './models/User';
export default (store) => {
const manager = new Manager({
resolver: new BaseResolver(),
drivers: {
fromSuperagent: new TectonicSuperagent(),
},
store,
});
manager.drivers.fromSuperagent([
...userEndpoints,
]);
return manager;
};
Simple! However, the manager.drivers
in setupManager
is undefined. Then it show me an error:
TypeError: Cannot read property 'fromSuperagent' of undefined
.
I have looked in the manager
variable and found out that there's a function called fromSuperagent
. But nothing of drivers
. Look:
Manager {
cache:
Cache {
store:
{ dispatch: [Function],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer] } },
store:
{ dispatch: [Function],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer] },
resolver:
BaseResolver {
satisfiabilityChain:
[ [Function: doesSourceSatisfyQueryParams],
[Function: doesSourceSatisfyQueryModel],
[Function: doesSourceSatisfyAllQueryFields],
[Function: doesSourceSatisfyQueryReturnType],
[Function: doesSourceSatisfyQueryType] ],
queries: {},
queriesInFlight: {},
statusMap: {},
store:
{ dispatch: [Function],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer] },
cache: Cache { store: [Object] } },
sources: Sources { definitions: Map {} },
fromSuperagent: [Function] }
I tried to call the function:
manager.fromSuperagent([...]);
But this also throws me an error Source definition must be comrpised of models, such as Model.list()
.
Am I doing something wrong?
EDIT
I'm using v1.3.2.
Tests in e923d94.
http://redux-form.com/5.2.3/#/examples/initializing-from-state?_k=ohcnwl
Using tectonic, we need to see if we can initialize a redux form from tectonic query/model state and create/update a form.
Copy of #58 for V1's EOL
We check each query to see if we have cached data already available to skip unnecessary API requests. If so, this data is passed to the component
is what I'm referencing. In my case I have a Table
component which loads a list and renders each TableRow
with an id
prop. Each TableRow
loads an item and display its content. The problem I'm experiencing is that a network request is being made for each TableRow
rather than the record being passed from the store.
mutate
will mutate data before being passed into the component:
@load({
users: User.getList().mutate(users => {
const byId = {};
users.forEach(u => byId[u.id] = u);
return byId;
}),
})
class Users extends Component {
}
Each mutator can be kept in a separate file for reusability. We should let mutator functions chain to keep composability of mutator functions optimal. Can we also automatically memoize these functions, or store the resulting data in a reducer?
Considerations:
Cache-Control: no-cache
headers or max-age=0
we should always re-request data.props
method could ignore cache information - this is more for the resolver to know whether we should re-requestWe should add etag support to queries.
If-None-Match
header alongside the requestThe driver needs to call success() to indicate the request worked. success()
currently updates the data in the reducer and the model IDs returned from the source. In this instance, no models are returned from the source — we're using what was previously there.
How should a driver indicate that:
undefined
as passed into success()Right now the Query Statuses have just a string for each state:
Query(Model: team, Fields: *, Params: {}, Body: undefined, QueryType: GET), ReturnType: list):"SUCCESS"
Instead of a simple string, it would be good to have something like:
// { type: 'SUCCESS', status: '200', message: '' }
// { type: 'ERROR', status: '404', message: 'Object not found: <blah id>' }
// { type: 'ERROR', status: '500', message: 'Internal server error' }
// { type: 'ERROR', status: '400', message: 'The name of that thing should this' }
The query
prop should be the parent of createModel, updateModel etc. using the same options but with no default queryType
parameter.
All params in query
should be supported from these options.
Error when querytype is update/delete and no modelID is specified
Components should be able to declaratively say whether they want to ignore the cache and re-request all data in their decorator.
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.