Ce document présente cozy-client à la fois dans son état actuel et dans sa vision, et propose une roadmap des évolutions souhaitables. L'objectif est double : informer et recueillir du feedback d'une part, mais aussi et surtout permettre à l'ensemble des devs front Cozy de contribuer à cette évolution.
- Origine du projet
- Présentation
- Evolution
- Vision
- Premiers pas
- Se débarrasser du middleware spécifique
- Evolution de
cozyConnectet des props injectĂ©es [BREAKING CHANGE!] - EmpĂȘcher une mĂȘme requĂȘte d'ĂȘtre exĂ©cutĂ©e plusieurs fois en parallĂšle
- Injecter plus d'actions de mise Ă jour via
cozyConnect - Simplification/généralisation des reducers et actions [ASSEZ GROS MORCEAU]
- Par la suite
cozy-client (anciennement redux-cozy-client) est nĂ© de la volontĂ© de limiter au maximum le boilerplate typique des actions asynchrones avec redux-thunk dans nos applications et de centraliser dans le store redux l'Ă©tat des diffĂ©rents fetches exĂ©cutĂ©s via cozy-client-js et d'y stocker les documents fetchĂ©s de maniĂšre normalisĂ©e. Il va donc au delĂ de cozy-client-js en proposant un moyen de "brancher" des composants (p)React sur des donnĂ©es issues de la stack cozy et de disposer de toutes les infos nĂ©cessaires pour l'UI (la requĂȘte est-elle en cours / doit-on afficher un spinner ? Y a t-il d'autres documents Ă fetcher ? Combien y en t-il au total sur le serveur ? etc...).
cozy-client est actuellement fait de 4 parties :
- une couche basse, le client en lui-mĂȘme, qui s'appuie sur 2 adapters pour rĂ©cupĂ©rer les donnĂ©es depuis 2 sources diffĂ©rentes :
 -
CozyStackAdapterqui wrappecozy-client-jspour récupérer des données depuis la stack,  -PouchDBAdapterpour récupérer des données depuis un pouch local synchronisé avec la stack.  Le client se base sur sa config (doctypes.offline) pour déterminer l'adapter à utiliser pour chaque appel.
import { CozyClient } from 'cozy-client'
const client = new CozyClient({...})
client.fetchDocument('io.cozy.todos', 'e8354db7abfef08b7a14e43b2b106a9d')
.then(document => { /* do something with the document */ })- une couche haute qui expose des sélecteurs et créateurs d'actions
redux. Afin de rĂ©duire le boilerplate, les actions crĂ©es sont d'un format spĂ©cifique Ăcozy-clientet nĂ©cessite la mise en place d'un middleware spĂ©cifique pour ĂȘtre traitĂ©es. En effet, on distingue traditionnellement 2 types d'action creatorsredux: - les synchrones :
export const receiveData = (data) => ({
type: RECEIVE_DATA,
data
}) - les asynchrones, qui nécessitent le middleware redux-thunk et que l'on utilise pour fetcher des données en dispatchant des action synchrones aux instants clés du cycle de vie du fetch :
export const fetchDocument = (doctype, id) => (dispatch, getState) => {
dispatch({ type: FETCH_DOCUMENT, doctype, id })
return client.fetchDocument(doctype, id)
.then(resp => dispatch({ type: RECEIVE_DATA, data: resp.data }))
.catch(err => dispatch({ type: RECEIVE_ERROR, error: err }))
}Les actions asynchrones deviennent vite verbeuses, et pourtant elles ont un motif commun : on dispatche une action avant d'exécuter le fetch, on dispatche une action en cas de succÚs, et une autre en cas d'erreur. Afin d'alléger ces action creators, cozy-client expose des actions creators qui n'ont pas de propriété type mais types et une propriété promise contenant une fonction exécutable avec une instance du client passée en argument :
export const fetchDocument = (doctype, id) => ({
types: [FETCH_DOCUMENT, RECEIVE_DATA, RECEIVE_ERROR],
doctype,
id,
promise: client => client.fetchDocument(doctype, id)
})-
le middleware
reduxdecozy-client, qui reçoit une instance du client lors de son instantiation, reconnait ce format d'action et exécute lapromiseen dispatchant les actionstypes:const { types, promise } = action const [REQUEST, SUCCESS, FAILURE] = types next({ ...rest, type: REQUEST }) return promise(client, dispatch, getState)) .then( response => { next({ ...rest, response, type: SUCCESS }) return response }, error => { console.log(error) next({ ...rest, error, type: FAILURE }) } ) .catch(error => { console.error('MIDDLEWARE ERROR:', error) next({ ...rest, error, type: FAILURE }) }) }
-
un HOC,
cozyConnect, qui permet à un composant de décrire les données dont il a besoin (c'est la vision en tout cas). ConcrÚtement, il permet de se passer du boilerplate typique de l'utilisation duconnectdereact-redux, à savoir dispatcher les actions de fetches injectées grùce aumapDispatchToPropsdans lecomponentDidMount.
import React from 'react'
import { cozyConnect, fetchCollection } from 'cozy-client'
const TodoList = ({ todos }) => {
const { data, fetchStatus } = todos
if (fetchStatus !== 'loaded') {
return <h1>Loading...</h1>
}
return (
<ul>
{data.map(todo => <li>{todo.label}</li>)}
</ul>
)
}
const mapDocumentsToProps = (ownProps) => ({
todos: fetchCollection('todos', 'io.cozy.todos')
})
export default cozyConnect(mapDocumentsToProps)(TodoList)When we use cozyConnect to wrap a component, three things happen:
- The actions specified by the
mapDocumentsToPropsfunction (here thefetchCollectioncall) will be dispatched when the component mounts, resulting in the loading of data from the client-side store, or the server if the data is not in the store - Our component subscribes to the store, so that it is updated if the data changes
- props are injected into the component: in our case, a
todosprop. If we were to declarepropTypesthey would look like this:
TodoList.propTypes = {
todos: PropTypes.shape({
fetchStatus: PropTypes.string.isRequired,
data: PropTypes.array
})
}As seen above, cozyConnect will pass the result of the collection fetch to the wrapped component in a prop whose name is specified in the mapDocumentsToProps function. For collections fetches, the shape of the injected prop is the following:
data: an array of documentsfetchStatus: the status of the fetch (pending,loading,loadedorerror)lastFetch: when the last fetch occuredhasMore: the fetches being paginated, this property indicates if there are more documents to loadfetchMore: a function you can call to trigger the fetching of the next page of data
De maniĂšre gĂ©nĂ©rale, on a besoin pour un composant de fetcher un unique document (et ses relations, mais ceci est une autre histoire) ou une liste (filtrĂ©e et/ou triĂ©e ou non) de documents d'un mĂȘme doctype, que l'on appellera collection. fetchCollection est l'action creator dont le dispatch permet de rĂ©cupĂ©rer une liste de documents. Son premier argument est le nom de la collection (par exemple timeline pour la liste des photos rĂ©centes), le deuxiĂšme le doctype et le troisiĂšme les options de la requĂȘte :
export default cozyConnect(ownProps => ({
photos: fetchCollection(
'timeline',
'io.cozy.files',
{
fields: ['dir_id', 'name', 'size', 'updated_at', 'metadata'],
selector: {
class: 'image',
trashed: false
},
sort: {
'metadata.datetime': 'desc'
}
}
)
}))(Timeline)Afin de persister le résultat de ces fetches dans le store de maniÚre normalisée, on stocke d'un cÎté tous les documents regroupés par doctypes et de l'autre des listes d'IDs correspondant aux collections fetchées. On a donc un store ressemblant à :
{
cozy: {
collections: {
timeline: {
count: 214,
fetchStatus: 'loaded',
hasMore: true,
ids: [
...
],
lastFetch: ...,
options: ...,
type: 'io.cozy.files'
}
},
documents: {
'io.cozy.files': {
0e80e10d37e791a94ccb4f7cff2a9cfe: {
_id: 0e80e10d37e791a94ccb4f7cff2a9cfe,
...
}
}
},
sharings: {
documents: [],
permissions: {}
},
synchronization: {
doctypes: {},
initialStatus: 'pending',
started: false
}
}
}Comme vous pouvez le constater ci-dessus, en raison de certaines spĂ©cificitĂ©s de l'API du partage, les sharings et permissions sont persistĂ©s Ă part. C'est loin d'ĂȘtre idĂ©al, et suite aux derniers travaux du back sur cette API, on pourrait envisager de normaliser ça.
L'architecture actuelle de cozy-client nous a déjà permis de bien réduire le boilerplate redux dans Photos, mais elle présente 2 problÚmes :
- une certaine rigidité (format des actions par ex.) qui pousse trop de responsabilités vers la couche basse, le client (choix de l'adaptateur et donc de la source de données par ex.). Conséquence : l'ajout de certaines fonctionnalités est plus compliqué ;
- beaucoup de "piÚces mobiles" (actions, selectors) sont exposées, ce qui complique les refactos de la lib, et son utilisation par les devs.
Une influence majeure dans le développement de cozy-client fût Apollo, un client GraphQL pour JS, et en particulier sa couche d'intégration avec React. Apollo propose en effet de nombreuses fonctionnalités trÚs pratiques pour le développeur, comme les fetch policies ou l'optimistic UI, fonctionnalités qu'il serait trÚs intéressant d'avoir dans cozy-client. Hors, Apollo n'expose pas (ou plus) du tout sa couche redux. C'est ce qui fait que son API reste simple à utiliser.
Ce que l'on vise donc avec cozy-client est une lib exposant seulement 2 piĂšces :
- un client, proche de
cozy-client-jsmais avec une API repensée, - un HOC de "binding" de données cozy avec des composants React.
On notera Ă©galement qu'il existe des similitudes entre la raison d'ĂȘtre de GraphQL, Ă savoir un langage de requĂȘte sur n'importe quelle API cĂŽtĂ© serveur, et nos besoins Ă nous, dĂ©veloppant sur la stack cozy : en effet, pour afficher une page, nous avons en gĂ©nĂ©ral besoin de requĂȘter diffĂ©rents types de donnĂ©es depuis la stack. Par exemple, la liste des albums de Photos nĂ©cessite de fetcher :
- les albums
- les albums partagés
- les fichiers référencés par les albums
Comme nous venons de le voir, actuellement nous passons à cozyConnect un objet dont les propriétés sont des actions dont le dispatch va entrainer le fetch des données correspondantes :
const mapDocumentsToProps = ownProps => ({
albums: fetchAlbums(),
sharings: fetchSharedAlbums()
})
export default cozyConnect(mapDocumentsToProps)(AlbumsView)Mais ces actions de fetch forment un tout indissociable : ce sont des fragments d'une seule et mĂȘme requĂȘte (pour reprendre des termes GraphQL). Donc au final, cet objet passĂ© Ă cozyConnect est une forme de description de requĂȘte, une QueryDefinition ;) Que l'on pourrait exprimer de façon plus Ă©lĂ©gante, par exemple :
const mapDocumentsToProps = ownProps => ({
albums: all('io.cozy.albums').include([ 'files', 'shared'])
})
export default cozyConnect(mapDocumentsToProps)(AlbumsView)Ce que l'on souhaite obtenir au final (et qui est esquissĂ© ci-dessus), c'est un DSL gĂ©nĂ©rant des dĂ©finitions de "requĂȘtes cozy", et que ces QueryDefinition ne soient plus des actions redux (car manipuler des actions et action creators redux comme on le fait pour l'instant, ça sent un peu la leaky abstraction), mais des objets dĂ©crivant Ă cozy-client les donnĂ©es qu'il doit fetcher pour rĂ©pondre Ă la requĂȘte. On doit donc introduire un nouvel Ă©lĂ©ment dont le rĂŽle sera :
- d'interprĂ©ter ces dĂ©finitions de requĂȘtes,
- d'appeller des méthodes du/des clients pour fetcher les données (plutÎt que de les inclure dans les actions, ce qui impose d'avoir un middleware et met trop de responsabilités sur le client, en particulier de devoir gérer 2 sources de données distincts),
- de dispatcher des actions pour mettre Ă jour le store et notamment l'Ă©tat des diffĂ©rentes requĂȘtes (rĂŽle du middleware actuellement),
- de s'assurer qu'on n'exĂ©cute pas pour rien plusieurs fois la mĂȘme requĂȘte, et ce, sans forcer l'utilisateur Ă prĂ©ciser un nom pour la requĂȘte comme c'est le cas actuellement pour les collections. C'est possible par une simple comparaison profonde des
QueryDefinition, objets qui ne seront jamais trÚs gros de toute façon.
Cet élément central pourrait s'appeller QueryManager (comme dans Apollo ;), à moins que quelqu'un ait une meilleure idée.
Comme expliqué plus haut, cozy-client nécessite actuellement un middleware redux spécifique : ce middleware régle de façon plutÎt élégante le problÚme du boilerplate redux typique des actions asynchrones car il sait gérer les actions types: [<TRUC>_REQUEST, <TRUC>_SUCCESS, <TRUC_ERROR] qu'émet cozy-client. Mais au final, cela apporte plus de contraintes qu'autre chose, notamment parce que cela impose un boilerplate de config redux particulier, mais aussi parce que cela force un cadre trÚs rigide pour nos actions redux.
S'en débarasser serait l'occasion d'introduire ce QueryManager, qui serait instancié et fourni en contexte par le CozyProvider :
import { Component } from 'react'
import PropTypes from 'prop-types'
import QueryManager from './QueryManager'
export default class CozyProvider extends Component {
static propTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}),
client: PropTypes.object.isRequired,
children: PropTypes.element.isRequired
}
static childContextTypes = {
store: PropTypes.object,
client: PropTypes.object.isRequired
}
static contextTypes = {
store: PropTypes.object
}
constructor(props) {
super(props)
this.queryManager = new QueryManager(props.client, props.store)
}
getChildContext() {
return {
store: this.props.store || this.context.store,
client: this.props.client,
queryManager: this.queryManager
}
}
render() {
return (this.props.children && this.props.children[0]) || null
}
}Dans un premier temps, le QueryManager se contente donc de remplir le rĂŽle du middleware, et devra ĂȘtre appellĂ© par cozyConnect pour dispatcher sous forme thunk les actions de fetch de cozy-client.
Aujourd'hui, lorsqu'on utilise connect ainsi :
export default cozyConnect(ownProps => ({
albums: fetchAlbums(),
sharings: fetchSharedAlbums()
}))(AlbumsView)On rĂ©cupĂšre 2 props dans AlbumsView, albums et sharings, avec chacun un fetchStatus, qui de surcroit change d'Ă©tat Ă des moments diffĂ©rents, d'oĂč des rerenders et flashes d'UI. La raison en est que connect se contente de dispatcher les actions de fetch retournĂ©es par fetchAlbums et fetchSharedAlbums.
Il nous faut dĂ©sormais considĂ©rer l'objet retournĂ© en argument de connect comme un objet reprĂ©sentant une seule et mĂȘme query, certes composĂ©e de plusieurs fetches car c'est le lot de toute API REST.
L'Ă©volution souhaitĂ©e ici est donc de modifier le QueryManager afin qu'il gĂšre l'exĂ©cution des diffĂ©rents fetches dans le contexte d'une requĂȘte dont il persiste le statut dans le store via un nouveau reducer (ou pas : dans un premier temps on pourrait se contenter de dĂ©terminer le status de la requĂȘte Ă partir des fetchStatus des diffĂ©rents fetches). connect devra ĂȘtre modifiĂ© afin qu'une unique prop data soit injectĂ©e, de la forme :
data: {
albums: [...],
sharings: [...],
loading: false,
error: null,
refetch() { ... },
fetchMore() { ... }
}Dans Drive ou Photos, il peut arriver (par exemple lorsqu'on accĂšde au contenu d'un album via une URL) que les mĂȘmes fetches soient dispatchĂ©es plusieurs fois. L'idĂ©e ici est donc de faire en sorte que le QueryManager gĂ©nĂšre une ID pour chaque requĂȘte et stocke dans le store l'ID ainsi que la dĂ©finition de la requĂȘte, Ă savoir l'objet pour l'instant composĂ© de plusieurs actions de fetches. Lorsqu'on exĂ©cute une requĂȘte, on fait une comparaison profonde de sa dĂ©finition avec celles prĂ©sentes dans le store, et si la requĂȘte est dĂ©jĂ prĂ©sente, on en utilise l'ID et on se "branche" sur le rĂ©sultat de cette requĂȘte.
Pour l'instant, les actions de "C(R)UD" dont le composant a besoin doivent ĂȘtre passĂ©es en 2Ăšme argument de cozyConnect, via un mapDispatchToProps typique de redux :
const mapDocumentsToProps = ownProps => ({
album: fetchAlbum(ownProps.router.params.albumId)
})
export const mapDispatchToProps = (dispatch, ownProps) => ({
updateAlbum: album => dispatch(updateAlbum(album))
})
export default cozyConnect(mapDocumentsToProps, mapDispatchToProps)(AlbumPhotos)LĂ encore, l'abstraction fuit, et on pourrait s'en passer Ă©tant donnĂ© qu'on peut facilement dĂ©terminer Ă partir de la requĂȘte les actions possibles sur les rĂ©sultats de celle-ci.
L'idée est donc de modifier cozyConnect de telle façon qu'il injecte plus d'actions à l'image du fetchMore :
data: {
albums: [...],
sharings: [...],
loading: false,
error: null,
refetch(),
fetchMore(),
create(<properties>),
update(<id>, <properties>),
destroy(<id>),
shareWith(<document>, <recipients>),
shareByLink(<document>),
revokeFor(<document>, <recipients>),
revokeLink(<document>),
...
}Notez bien que les actions Ă injecter diffĂšrent en fonction de la requĂȘte (et de ses fragments) : ci-dessus on injecte shareWith, shareByLink, etc... car le fetch des albums partagĂ©s fait partie de la requĂȘte.
Si l'on fetche un simple album et ses fichiers référencés, les actions injectées sont différentes (et plus adaptées) :
data: {
album: <document>,
photos: [...] // les fichiers référencés,
loading: false,
error: null,
refetch(),
fetchMore(),
update(<properties>), // pas besoin d'ID, cette action est pré-bindée sur l'album fetché
destroy(),
addFile(<file>), // actions sur les fichiers référencés
removeFile(<file>),
...
}Actuellement, nous avons des reducers spĂ©cifiques pour les diffĂ©rents types de fetches : documents, sharings, fichiers rĂ©fĂ©rencĂ©s... Nous avons Ă©galement un grand nombre d'actions diffĂ©rentes, toutes du type types: [<TRUC>_REQUEST, <TRUC>_SUCCESS, <TRUC>_ERROR]. Il y a clairement un motif autour des requĂȘtes et des mises Ă jour asynchrones, Ă savoir que l'on Ă©met avant une action pour "flagger" le fetch Ă venir dans le store, qu'en cas de succĂšs, on Ă©met une action dont la charge utile permet de mettre Ă jour le store, et qu'en cas d'Ă©chec on Ă©met une action pour "flagger" le fetch comme Ă©tant en erreur. Il conviendrait donc de factoriser quelque part ce motif plutĂŽt que d'avoir des actions aussi verbeuses.
D'autre part, et comme nous l'avons vu plus haut, l'objectif "ultime" est de ne plus exposer de crĂ©ateurs d'actions redux, mais un DSL de crĂ©ation de dĂ©finitions de requĂȘtes, dĂ©finitions qui seront passĂ©es en argument de cozyConnect. Ben c'est le moment ! ;)
L'idĂ©e est d'employer une abstraction inspirĂ©e de GraphQL ; en GraphQL, il n'y a en effet que 2 types d'opĂ©rations : les Query pour rĂ©cupĂ©rer des donnĂ©es et les Mutation pour les mettre Ă jour. Nous allons donc remplacer nos action creators par des QueryDefinition creators et des MutationDefinition creators : on considĂ©rera dĂ©sormais qu'une dĂ©finition de requĂȘte passĂ©e en argument de cozyConnect est composĂ©e de N fragments correspondants aux diffĂ©rents fetches nĂ©cessaires pour rĂ©pondre Ă la requĂȘte (ex. pour un album : fetch du document album, fetch des albums partagĂ©s, fetch des fichiers rĂ©fĂ©rences par l'album). Nos action creators deviendraient ainsi quelque chose dans ce genre :
export const FragmentTypes = {
DOCUMENT: 'DOCUMENT',
COLLECTION: 'COLLECTION',
REFERENCED_FILES: 'REFERENCED_FILES'
}
export const fetchCollection = (doctype, options = {}, skip = 0) => ({
fragmentType: FragmentTypes.COLLECTION,
doctype,
...options,
skip,
promise: client => client.getCollection(doctype).find(options, skip)
})
export const fetchReferencedFiles = (doc, skip = 0) => ({
fragmentType: FragmentTypes.REFERENCED_FILES,
referencedBy: doc,
skip,
promise: client =>
client.getCollection('io.cozy.files').findReferencedBy(doc, skip)
})
export const fetchDocument = (doctype, id) => ({
fragmentType: FragmentTypes.DOCUMENT,
doctype,
id,
promise: client => client.getCollection(doctype).get(id)
})
// etc...
export const MutationTypes = {
CREATE: 'CREATE',
UPDATE: 'UPDATE',
DESTROY: 'DESTROY',
SHARE: 'SHARE'
// etc...
}
export const createDocument = (doctype, properties) => ({
mutationType: MutationTypes.CREATE,
doctype,
properties,
promise: client => client.getCollection(doctype).create(properties)
})
// etc...Et c'est donc le QueryManager qui va désormais dispatcher des actions génériques pour mettre à jour le store quand il exécute une query ou une mutation. En pseudocode, cela donnerait :
class QueryManager {
// ...
query(queryId, queryDefinition) {
// note: le queryId est généré ailleurs
this.store.dispatch({ type: INIT_QUERY, queryId })
return Promise.all(
Object.keys(queryDefinition).map(fragmentName => {
const fragment = queryDefinition[fragmentName]
const fragmentId = `${queryId}#${fragmentName}`
this.store.dispatch({
type: FETCH_QUERY_FRAGMENT,
fragmentId,
...fragment
})
return = fragment
.promise(this.client)
.then(response => {
this.store.dispatch({
type: RECEIVE_QUERY_FRAGMENT,
fragmentId,
response
})
})
.catch(error => {
this.store.dispatch({
type: RECEIVE_QUERY_ERROR,
fragmentId,
error
})
})
})
).then(() => this.store.dispatch({ type: RECEIVE_QUERY_RESULT, queryId }))
.catch(() => this.store.dispatch({ type: RECEIVE_QUERY_ERROR, queryId }))
}
// ...
}TBD
TBD
Par partie cliente "pure", j'entends le code qui parle Ă la stack, donc la partie non-redux. Aujourd'hui, pour fetcher des documents avec ce client, on fait :
const resp = client.fetchDocuments('io.cozy.albums')C'est donc le client qui détermine un adapter (stack ou pouch) à utiliser pour récupérer les données, en fonction de la config du client et en particulier des éventuels doctypes définis comme étant offline. Remarquez aussi que l'API du client est vaste et un peu fourre-tout avec les doctypes particuliers comme les fichiers ou les partages (client.fetchDocuments(), client.fetchFiles(), client.fetchReferencedFiles(), client.fetchSharings()
L'idĂ©e est donc d'avoir simplement 2 clients distincts : un pour la stack et un pour PouchDB, avec la mĂȘme API simple :
const stackClient = new CozyStackClient(...)
const resp = stackClient.getCollection('io.cozy.albums').all()Le client possÚde une méthode getCollection qui retourne pour un doctype donné un objet avec une API générique :
all()find(...)create(...)update(...)destroy(...)
Pour la plupart des doctypes, on utilisera donc une classe Collection gĂ©nĂ©rique. Pour les cas particuliers comme les fichiers ou les partages, on implĂ©mentera des classes spĂ©cifiques qui devront ĂȘtre register par le client et qui possĂšderont la mĂȘme API de base, mais aussi quelques mĂ©thodes supplĂ©mentaires spĂ©cifiques (ex: findReferenced pour les fichiers ?)
Enfin, ce sera donc au QueryManager dĂ©sormais de savoir quel client (stack ou pouch) utiliser pour une requĂȘte en fonction de la fetchPolicy choisie (cf ci-dessous).
Afin de gagner en efficacitĂ©, il conviendrait de pouvoir rĂ©cupĂ©rer le rĂ©sultat d'une requĂȘte dans le store redux si celle-ci a dĂ©jĂ Ă©tĂ© exĂ©cutĂ©e rĂ©cemment. On pourrait aussi vouloir requĂȘter le Pouch et si pas de rĂ©sultats parce que la synchro est encore en cours, faire un fallback sur la stack. C'est l'objectif de cette option fetchPolicy :
export default cozyConnect(ownProps => ({
operations: all('io.cozy.bank.operations')
}), {
fetchPolicy: 'cache-and-network'
})(OperationList)Dans l'exemple ci-dessus, le QueryManager vĂ©rifierait en premier si tous les fragments nĂ©cessaires pour rĂ©pondre Ă la requĂȘte sont dĂ©jĂ dans le store, et si oui, retournerait directement le rĂ©sultat de la requĂȘte, avant d'exĂ©cuter la requĂȘte via la stack et de rafraichir ensuite le rĂ©sultat.
Cf https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-fetchPolicy
Remplacer les action creators actuels (fetchCollection(), ...) ou les queryDefinitions creators qui les auront remplacĂ©s par un DSL fonctionnel permettant de façonner les dĂ©finitions de requĂȘtes :
const query = find('io.cozy.albums').where({ name: 'foo' })Il n'est pas idéal de dépendre encore de cozy-client-js. Il faudrait donc rapatrier tout le code nécessaire pour les fetches dans cozy-client, ce qui inclut le code d'authent.
TBD
Besoin d'un EXPLAIN pour aider l'user à comprendre pourquoi il n'a pas les données qu'il veut
TBD
TBD
TBD
_[posté à https://gist.github.com/enguerran/e4919167898e3aa611cc63d220bb7701#file-questions-suite-a-reunion-md]
mon avis sur le state interne de cozy-client
Le state redux que se gĂšre cozy-client doit ĂȘtre interne :
questions
apollo
question
Apollo ? Popularité ? Apollo pour serveur REST plutÎt que graphQL ?
réponse
packaging
question
Packaging ? plusieurs libs ? Quid des dev angular/backbone/vue ?
réponse