Giter Club home page Giter Club logo

tectonic's Issues

Angular 2 support

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 :)

Discovered APIs

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:

  1. Is there any way to handle discovered API resources? The implicit way of defining dependencies is cool, but let's say the whole URL of the next request is unavailable before the first request continues?
// 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.

  1. 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.

  2. Is there any way to normalize data somehow?

Thanks!

Create a websocket driver

In order to get live feeds for data we should create a websocket component.

Questions:

  • How do we differentiate feeds within queries and source definitions?

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?
])

Create model fails with no source definition found in some cases

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: )

load decorator throwing error when props update

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.

query.hash() is not function

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?

Make dumbResolver call the sourcedefinition's driver with the given query

IE:

sourceDef.driverFunc({
  query,
  sourceDef,
  success,
  fail
});

Where:

  • The driver function gets the source and query, plus success and fail functions
  • The driver calls success function with response data
  • The driver calls the fail function with error data
  • The success function must also know the query and source definition BUT it should only be called with the data in driver (partial application):

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 });
    }
  }

}

Defaulting model.fields.id to undefined produces error

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?

How do we know when to re-call failed queries?

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 })
})
...

Drivers should have "default params"

Each source definition should have a defaultParams object. The base resolver should inspect the query params and add defaults before passing to the driver.

Sideloading Data

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:

  1. How do we store and consume the other models returned?
  2. How would we cache those models?

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?

Complex API result ordering (eg searches, polymorphic and multiple model returns)

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?

Outdated README

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?

FAQs

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

load decorator causing React.PropTypes validation warnings

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.

Cannot read property 'fromSuperagent' of undefined

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.

Returning records from cache seems to not work.

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.

Add `mutate` to Query model

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?

Implement global caching and per-source caching.

Source caching

Considerations:

  • If an API response has Cache-Control: no-cache headers or max-age=0 we should always re-request data
  • When resolving a component with dependent data, we should ignore cache-control headers for re-requesting data (or we'll have an infinite loop).
  • The manager's .props method could ignore cache information - this is more for the resolver to know whether we should re-request

Request caching: add etag support

We should add etag support to queries.

Plan:

  1. Store the source ID and source response headers for each request (we currently only store the model IDs returned from the source)
  2. When a query is generated that has already been called and has an etag in its header send an If-None-Match header alongside the request
  3. If we get a 304 pull data from the cache

Questions:

The 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:

  • the call was successful
  • we should use the data from a previous API request, and not undefined as passed into success()

More granular tectonic Query Statuses

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' }

Create a `query` prop in decorator

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.