Apollo-link: [apollo-link-offline] Quelle est votre idée ?

Créé le 5 oct. 2017  ·  38Commentaires  ·  Source: apollographql/apollo-link

Bonjour, je serais très heureux de participer à cette fonctionnalité de build. Parce que j'en ai vraiment besoin pour le présent projet. Mais j'ai besoin de quelques conseils pour commencer. Quelqu'un a-t-il une idée?

enhancement

Commentaire le plus utile

Salut les gars, j'ai essayé de reconstituer une implémentation de base pour react-native qui a un comportement similaire à apollo-offline .
(ping https://github.com/Malpaux/apollo-offline/issues/14)

Vous pouvez le vérifier dans cet aperçu : https://gist.github.com/lachenmayer/2e364a5ca9ae0918eb032867d0c6720d

C'est une combinaison de :

Lorsque l'appareil se déconnecte, les demandes seront mises en file d'attente et seront retirées de la file d'attente lorsqu'il se reconnectera. ( apollo-link-queue )

Toute requête qui échoue avec une erreur réseau sera retentée (actuellement à l'infini). ( apollo-link-retry )
Notez que cela peut être dû au fait que l'appareil est hors ligne ou que le backend n'est tout simplement pas accessible (par exemple, s'il est en panne).

Cependant, si nous pouvons résoudre la requête à partir du cache, elle ne sera pas réessayée et les données du cache seront utilisées comme réponse. Ceci est implémenté dans la fonction optimisticFetchLink .

À mon avis, ce comportement de "récupération optimiste" est l'une des parties les plus importantes d'apollo-offline, et toute future implémentation de apollo-link-offline devrait le prendre en charge. Cela permet à l'utilisateur de continuer à utiliser l'application comme d'habitude hors ligne, tant que les données ont été récupérées et conservées à un moment donné. À mon avis, cela devrait être le comportement par défaut de la politique de récupération network-and-cache , mais malheureusement, il ne semble pas que cela changera de sitôt (voir https://github.com/apollographql/react-apollo/issues/ 604#issuecomment-355648596).

Si vous enregistrez l'essentiel sous offlineLink.js , vous pouvez l'utiliser comme suit :

import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'

// get this from https://gist.github.com/lachenmayer/2e364a5ca9ae0918eb032867d0c6720d
import { createOfflineLink } from './offlineLink'

const cache = new InMemoryCache()
const networkLink = createHttpLink()

const offlineLink = createOfflineLink({ cache })

const link = ApolloLink.from([
  // ... + other links ...
  offlineLink,
  networkLink,
])

const client = new ApolloClient({
  cache,
  link,
}

Problèmes connus / bits manquants

  • Cela ne fonctionne qu'avec react-native pour le moment. Le contrôle isOnline doit être supprimé pour rendre cette plateforme multiplateforme.
  • Si vous n'avez qu'une réponse partielle dans le cache, vous obtiendrez une promesse rejetée quelque part en bas de la ligne vous indiquant que la réponse est incomplète. Je n'ai pas encore cherché à savoir comment résoudre ce problème, ni comment apollo-offline résout cela. Si vous voulez essayer de résoudre ce problème, j'apprécierais tous les pointeurs à ce sujet.
  • Vous ne pouvez actuellement modifier aucun des paramètres de nouvelle tentative - cela devrait être ajouté en tant que paramètre de configuration supplémentaire.
  • Vous n'avez aucun contrôle sur l'extraction optimiste. Vous devriez pouvoir le définir indépendamment pour chaque requête. Je ne suis pas un grand fan de l'implémentation d'apollo-offline de cela (définir une variable de requête {__online_: true} ), mais il devrait certainement y avoir un moyen de le faire.
  • Il n'y a évidemment pas non plus de tests.

J'apprécierais que tous ceux qui recherchent une solution à ce sujet l'essayent, et nous pouvons ensuite éventuellement le transformer en un module npm approprié avec des saisies, des tests, des documents et tout le reste. Ce comportement est très important à mon avis, et c'est vraiment dommage qu'apollo-client 2 n'ait pas encore de solution appropriée pour cela.

Joli ️

Tous les 38 commentaires

Que considéreriez-vous comme cas d'utilisation typique ?

En tant qu'application hors ligne :

  • Toutes les requêtes graphql sont ajoutées à la file d'attente. (peut être stocké dans le cache).
  • Lorsque l'application reçoit le signal en ligne, elle informe le lien pour soumettre à nouveau la demande précédente.

J'imagine qu'il s'utilise comme ceci :

import { NetInfo } from 'react-native';
import OfflineLink from 'apollo-link-offline';

const offlineLink = new OfflineLink({
  isOnlineAsync: () => NetInfo.isConnected.fetch(),
});

NetInfo.isConnected.addEventListener('change', (isConnected) => {
  if (isConnected) {
    offlineLink.resubmit();
  }
});

Je pense que ce serait une fonctionnalité incroyable. La façon dont je vois les choses potentiellement se produire serait qu'Apollo-link-offline définisse un ensemble de requêtes et de mutations qui doivent être utilisées hors ligne.

Une fois celles-ci définies, en arrière-plan de l'application, nous pourrions récupérer toutes les données des requêtes et les stocker dans un stockage local. Pour les mutations, nous aurions besoin de tous les champs créés dans le stockage local afin de pouvoir stocker toutes les données créées hors ligne.

De cette façon, nous pouvons toujours exécuter des fonctionnalités hors ligne tout en stockant les données dans un cache local.
Nous réussirions également les tests PWA de Google si nous pouvions établir un lien avec les Service Workers
Ensuite, lorsque l'application revient en ligne, toutes les mutations qui se sont produites sur le cache local peuvent être transmises à l'API.

De toute évidence, il faudrait mettre en place une forme de protection de la concurrence.

Mon souci est que l'utilisateur A (hors ligne) met à jour le tableau X tandis que l'utilisateur B (en ligne) a mis à jour le tableau X alors que l'utilisateur A était hors ligne. Nous aurions besoin d'une forme de règles de concurrence ici. Je suppose que cela pourrait être déterminé par la date, mais cela ne résout pas le problème si l'utilisateur A remplace les données de l'utilisateur B sans que l'utilisateur B ne le sache.

Ou nous pourrions simplement échouer la mutation et laisser l'utilisateur se mettre à jour manuellement lorsqu'il est de retour en ligne.

Apollo Client 2 est incroyable, mais cette fonctionnalité serait les conneries absolues des chiens

Je serais aussi un grand fan de ça. Je sais qu'il y a eu diverses tentatives pour résoudre ce problème avec le magasin Redux 1.0, mais en faire un citoyen à part entière de l'écosystème Apollo 2.0 avec une approche cohérente serait assez merveilleux.

La plupart de ce qui a été couvert ici a déjà beaucoup de sens pour moi - en gros, je veux conserver mes données dans un stockage local, puis les extraire de là dans Apollo après coup. Secondairement, la mise en file d'attente des mutations.

En tant qu'objectif tertiaire, être capable d'aspirer encore plus de données en arrière-plan serait également formidable, et c'est quelque chose que je chercherai à faire malgré tout. En fait, j'aimerais également stocker presque toutes les données de l'utilisateur localement, juste pour accélérer les opérations de requête courantes, puis les réhydrater pour une précision totale, si le réseau le permet. Donc, une sorte de solution pour cet angle de chargement avec impatience serait formidable, d'autant plus que les Service Workers commencent à devenir viables avec Safari les ayant enfin en avant-première technique.

Je suis moi aussi excité à ce sujet. @danieljvdm Vous avez pris une longueur d'avance sur la persistance dans un problème du référentiel apollo-client .

Je pense que le RetryLink sera utile pour gérer les erreurs réseau et les tentatives, même si j'aimerais voir une sorte de chute comme redux-offline avait .

C'est plutôt cool. Le problème auquel @2WheelCoder a fait référence est une approche précoce et peut-être naïve davantage axée sur la persistance du cache plutôt que sur une prise en charge complète hors ligne (requêtes/mutations de file d'attente, etc.).

Cela pourrait toujours être un endroit décent pour commencer et construire par-dessus. Ce que j'ai souligné dans ce numéro, c'est que je n'ai actuellement pas un bon moyen de surveiller le cache pour les modifications. J'ai en fait essayé de mettre en œuvre la suggestion initiale de @ 2WheelCoder plus tôt dans la j'ai rencontré des problèmes similaires. Le problème avec la création d'un ApolloLink pour « surveiller les modifications du magasin » est qu'il ne vit que dans le contexte entre une action client et l'écriture de cache qui s'ensuit. Je peux donc intercepter la requête avant qu'elle ne sorte (middleware) et l'intercepter avant qu'elle ne soit écrite dans le cache (afterware) mais je ne sais pas quand elle a été écrite - je ne peux que deviner (mon hack actuel est un délai d'attente de 1000 ms ).

Un autre défaut fatal lié à l'utilisation d'un lien pour la persistance hors ligne est qu'il ne prend pas en compte les abonnements. Un lien d'abonnement ne se déclenchera que lorsque l'abonnement est ouvert, pas lors d'événements ultérieurs (il y a peut-être un moyen de le faire que j'ai manqué ?).

C'est essentiellement là où j'en suis en ce moment, et je suis assez coincé - je pense que pour aller plus loin, je devrais peut-être ouvrir un PR à apollo-cache-inmemory.

@danieljvdm Je suis au même endroit et je pense qu'il est logique de gérer la persistance directement sur le cache après avoir rencontré le même problème que vous. Le HttpLink n'est tout simplement pas adapté car l'observable se termine après la demande mais (généralement) avant les mises à jour du cache.

Bien que cela puisse valoir la peine d'ouvrir un PR sur apollo-cache-inmemory, je me demande également s'il ne serait pas préférable de l'intégrer dans un nouveau projet. La modularité d'Apollo 2 autour du cache et du lien semble suggérer que si vous avez besoin d'un autre type de cache, vous pouvez simplement le construire. Peut-être qu'un problème sur apollo-client est la prochaine étape (je ne pense pas qu'apollo-cache-inmemory ait son propre repo) ?

Désolé d'être un peu hors sujet à partir des liens ici.

@holman auriez-vous une référence sur le travailleur de service dans l'aperçu de Safari Tech ?

@sedubois, il est tombé (un peu soudainement) il y a un mois ou deux. C'est en avant - reste encore pas mal de chemin à parcourir .

@2WheelCoder J'aime l'idée d'un client PR à apollo pour un nouveau cache. Peut-être apollo-offline-cache qui peut étendre apollo-cache-inmemory ? Je serais prêt à aider à travailler là-dessus. Vous voulez ouvrir un problème là-bas ?

@2WheelCoder @danieljvdm qu'en est-il de l'utilisation de LocalForage pour la persistance https://github.com/localForage/localForage Si vous ouvrez un PR, je serais prêt à donner un coup de main

@danieljvdm Oui,

@Eishpirate Tout à fait d'accord, LocalForage est génial. Je pense que la façon dont redux-persist a géré la sauvegarde du magasin était assez solide et peut probablement constituer un modèle décent à suivre dans apollo-offline-cache .

Mes amis, j'utilise le package redux persist pour enregistrer les données du magasin dans AsyncStorage natif réactif. Comment je fais ça maintenant ? Une idée?

@Eishpirate : localforage fonctionne-t-il pour réagir natif ? En regardant le tableau de compatibilité de la bibliothèque, je ne trouve rien à propos de react native. https://github.com/localForage/localForage/wiki/Supported-Browsers-Platforms

Ce serait très triste si la fonction hors ligne d'apollo ne fonctionnait pas pour les applications.

@timLoewel c'est un très bon point. Je n'avais pas vraiment pensé aussi loin que React Native. Ne croyez pas qu'il le fasse explicitement.

Cela peut entraîner des problèmes inattendus plus tard. Je ne sais pas s'il existe une alternative viable sans avoir à réinventer la roue

Article intéressant qui pourrait être pertinent pour cette discussion https://blog.logrocket.com/building-an-offline-first-app-with-react-and-rxdb-e97a1fa64356?t=now

Il semble vraiment prometteur d'utiliser rxdb comme base de données hors ligne. Ceci est compatible avec React, React-Native, Vue, Angular, les options les plus populaires.

Le problème que je trouve cependant est qu'il doit se synchroniser avec une base de données noSQL comme PouchDB (dérivé de CouchDB). Dans mon cas d'utilisation, j'utilise une base de données SQL relationnelle (Postgres) et toutes mes validations de données se produisent au niveau de la base de données. Si je devais utiliser ce système, je devrais maintenir la validation via le jsonschema.

Il ne suit pas les principes DRY et introduirait certainement des problèmes.

Une autre option que j'ai trouvée est Kinto http://docs.kinto-storage.org/en/stable/index.html qui fonctionne avec Postgres et devrait donc éviter certaines des couches de complexité supplémentaires que rxdb introduirait. Kinto utilise HTTP, nous pourrions donc théoriquement le brancher sur Apollo-link-http https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http

On dirait qu'une grande partie du travail sera liée à la mise en cache.

Et le redux ? (Je suis serieux)

Un package apollo-link-offline , basé sur le travail apollo-offline et...
Un paquet apollo-cache-redux . Utilisez redux-offline/redux-persist comme le fait apollo-offline maintenant.

Apollo a créé le inmemory-cache comme solution générique par défaut - je peux comprendre pourquoi ils ne voulaient pas être liés si étroitement avec une autre bibliothèque. Mais la solution redux/redux-offline/redux-persis est tellement éprouvée... c'est toujours un excellent choix. Le développement est très actif pour tous les trois.

EDIT : apollo-cache-redux existe maintenant, grâce à @rportugal

@giautm J'ai implémenté à peu près exactement ce que vous décrivez pour un petit projet à moi il y a quelque temps, et j'ai décidé de le publier sous la forme d'un package très simple : apollo-link-queue . Si l'un d'entre vous a des idées d'amélioration, je serais ravi d'avoir vos suggestions ou contributions.

S'il vous plaît quel est l'état de ce problème? Y a-t-il quelque chose que nous pouvons utiliser pour les éléments hors ligne qui proviennent d'Apollo et qui ne soient pas liés à Redux Offline ?

@smithaitufe ,
Oui, vous pouvez utiliser apollo-cache-persist .

@Gregor1971
Merci pour cette référence. Je vais l'essayer et soumettre mes observations.
En apparence, il a l'air cool et facile à mettre en œuvre.

En quoi cela diffère-t-il d' apollo-link-queue

Salut les gars, j'ai essayé de reconstituer une implémentation de base pour react-native qui a un comportement similaire à apollo-offline .
(ping https://github.com/Malpaux/apollo-offline/issues/14)

Vous pouvez le vérifier dans cet aperçu : https://gist.github.com/lachenmayer/2e364a5ca9ae0918eb032867d0c6720d

C'est une combinaison de :

Lorsque l'appareil se déconnecte, les demandes seront mises en file d'attente et seront retirées de la file d'attente lorsqu'il se reconnectera. ( apollo-link-queue )

Toute requête qui échoue avec une erreur réseau sera retentée (actuellement à l'infini). ( apollo-link-retry )
Notez que cela peut être dû au fait que l'appareil est hors ligne ou que le backend n'est tout simplement pas accessible (par exemple, s'il est en panne).

Cependant, si nous pouvons résoudre la requête à partir du cache, elle ne sera pas réessayée et les données du cache seront utilisées comme réponse. Ceci est implémenté dans la fonction optimisticFetchLink .

À mon avis, ce comportement de "récupération optimiste" est l'une des parties les plus importantes d'apollo-offline, et toute future implémentation de apollo-link-offline devrait le prendre en charge. Cela permet à l'utilisateur de continuer à utiliser l'application comme d'habitude hors ligne, tant que les données ont été récupérées et conservées à un moment donné. À mon avis, cela devrait être le comportement par défaut de la politique de récupération network-and-cache , mais malheureusement, il ne semble pas que cela changera de sitôt (voir https://github.com/apollographql/react-apollo/issues/ 604#issuecomment-355648596).

Si vous enregistrez l'essentiel sous offlineLink.js , vous pouvez l'utiliser comme suit :

import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'

// get this from https://gist.github.com/lachenmayer/2e364a5ca9ae0918eb032867d0c6720d
import { createOfflineLink } from './offlineLink'

const cache = new InMemoryCache()
const networkLink = createHttpLink()

const offlineLink = createOfflineLink({ cache })

const link = ApolloLink.from([
  // ... + other links ...
  offlineLink,
  networkLink,
])

const client = new ApolloClient({
  cache,
  link,
}

Problèmes connus / bits manquants

  • Cela ne fonctionne qu'avec react-native pour le moment. Le contrôle isOnline doit être supprimé pour rendre cette plateforme multiplateforme.
  • Si vous n'avez qu'une réponse partielle dans le cache, vous obtiendrez une promesse rejetée quelque part en bas de la ligne vous indiquant que la réponse est incomplète. Je n'ai pas encore cherché à savoir comment résoudre ce problème, ni comment apollo-offline résout cela. Si vous voulez essayer de résoudre ce problème, j'apprécierais tous les pointeurs à ce sujet.
  • Vous ne pouvez actuellement modifier aucun des paramètres de nouvelle tentative - cela devrait être ajouté en tant que paramètre de configuration supplémentaire.
  • Vous n'avez aucun contrôle sur l'extraction optimiste. Vous devriez pouvoir le définir indépendamment pour chaque requête. Je ne suis pas un grand fan de l'implémentation d'apollo-offline de cela (définir une variable de requête {__online_: true} ), mais il devrait certainement y avoir un moyen de le faire.
  • Il n'y a évidemment pas non plus de tests.

J'apprécierais que tous ceux qui recherchent une solution à ce sujet l'essayent, et nous pouvons ensuite éventuellement le transformer en un module npm approprié avec des saisies, des tests, des documents et tout le reste. Ce comportement est très important à mon avis, et c'est vraiment dommage qu'apollo-client 2 n'ait pas encore de solution appropriée pour cela.

Joli ️

@smithaitufe ,

En quoi cela diffère-t-il d'apollo-link-queue ?

Il semble qu'apollo-link-queue fasse des choses intelligentes en fonction de l'état de la connexion. apollo-cache-persist enregistre le cache ; permettant, par exemple, aux utilisateurs de démarrer et d'exécuter votre application lorsqu'ils sont déconnectés. Sans conserver le cache, votre application aurait besoin d'une connectivité pour démarrer,

Nous voudrons probablement les deux, ou mieux encore, la solution de @lachenmayer ci-dessus (qui utilise apollo-link-queue) et un cache persistant. (nous discutons dans les dépôts de liens, donc l'accent est mis ici sur cela)

@lachenmayer Dès le premier regard, votre approche semble très attrayante.

Je suis tout à fait d'accord avec vous sur la nécessité de quelque chose comme la fonction de récupération optimiste de apollo-offline . C'est en grande partie ce (ou plutôt son absence) qui m'a inspiré pour commencer apollo-offline .

Je ne suis moi-même pas tout à fait satisfait de la façon dont j'ai dû implémenter l'activation/la désactivation sélective de la fonction de récupération optimiste dans apollo-offline . Les variables de requête n'étaient pas le premier choix, mais elles semblaient être la plus pratique. Que proposeriez-vous ?

Les prochaines semaines vont être assez stressantes pour moi. Après cela, je serais plus qu'heureux de contribuer à la mise en œuvre d'une première solution hors ligne à jour pour Apollo - que ce soit une nouvelle version du package apollo-offline ou quelque chose comme apollo-link-offline .

Merci @MLPXBrachmann !
Je pense que la récupération optimiste devrait être contrôlée à l'aide de fetchPolicy - si je ne veux rien sortir du cache, je devrais pouvoir utiliser network-only .

@lachenmayer @MLPXBrachmann

Je suis plus que prêt et disposé à contribuer.

Je recherche moi aussi des options pour implémenter un comportement hors ligne dans une application Apollo. L'approche de @lachenmayer semble très prometteuse, mais comme elle repose sur apollo-link-retry, les mutations qui n'ont pas été appliquées avec succès ne seront pas persistantes, donc si la page est actualisée, toutes les données qui n'ont pas été validées sur le serveur seront jeté (je suppose que c'est la même chose sur react-native si l'application est suspendue). Y a-t-il des travaux ou au moins des discussions sur cet aspect?

@nicocrm, je vois... Pour le moment, la discussion sur le support hors ligne porte principalement sur ce problème. Je pense qu'apollo eux-mêmes ont d'autres priorités en ce moment, nous devrions donc créer un projet communautaire apollo-link-offline . J'espère que cela conduira à plus de discussions et de progrès qu'en ce moment 😄

@benseitz, je suis définitivement en faveur de cela. Il y a beaucoup d'intérêt pour la fonctionnalité donc je suis sûr qu'il y a de la place pour un projet communautaire - tant que je ne duplique pas ou ne fragmente pas les efforts existants, je peux créer une nouvelle équipe et un dépôt pour apollo-link-offline et inviter tous les intéressés . J'ai un intérêt personnel ainsi qu'un client qui insiste vraiment pour cette fonctionnalité, donc j'aurai quelques heures pour y consacrer. Je vais demander sur le repo apollo-offline pour voir s'ils veulent prendre les devants car ils ont beaucoup plus d'expérience.

J'ai certainement un intérêt personnel pour cela aussi - je serais heureux de discuter plus avant avec vous sur ce @nicocrm. Je n'ai pas remarqué de requêtes perdues jusqu'à présent sur react-native, mais je n'ai pas vraiment testé tout cela correctement (uniquement avec les requêtes jusqu'à présent et il semble que tout fonctionne correctement).

Je pense qu'il serait logique de démarrer un dépôt pour cela. @MLPXBrachmann qui a construit apollo-offline mentionné ci-dessus qu'il n'aura pas de temps à consacrer à l'amélioration de apollo-offline dans les prochaines semaines, et je pense qu'il est plus logique de l'appeler apollo-link-offline .

Je ne pense pas qu'il y ait de code réutilisable à partir de apollo-offline car il est très spécifique au redux.

@nicocrm je suis tout à fait d'accord avec toi. Les requêtes/mutations en file d'attente persistantes devraient certainement être quelque chose qu'un potentiel apollo-link-offline prend en charge.

@benseitz Je pense moi aussi que ce serait une bonne idée de commencer apollo-link-offline tant qu'effort communautaire et j'aimerais beaucoup participer à son développement.

@lachenmayer Vous avez tout à fait raison, la quantité de code partagée par apollo-offline et le package apollo-link-offline prévu sera probablement presque inexistant. Je pense cependant que bon nombre des concepts sous-jacents du premier s'appliquent toujours lors du développement d'une boîte à outils hors ligne dans l'univers Apollo 2.0.

Dans tous les cas, je serais heureux de discuter d'idées pour apollo-link-offline , de donner mon avis sur la mise en œuvre et - dès que mon emploi du temps se libère un peu - de contribuer également au code.

@MLPXBrachmann @lachenmayer @nicocrm Cela semble être la bonne façon de procéder !
Puisque n'importe qui peut ouvrir une chaîne publique sur Apollo Slack . Je suggérerais d'en ouvrir un appelé apollo-link-offline . Peut-être que cela rend la communication entre nous un peu plus facile. Toutes les décisions importantes doivent toujours être documentées dans les problèmes GitHub.

Êtes-vous d'accord pour que j'ouvre à la fois le repo et le canal slack ou l'un de vous souhaite-t-il le faire ?

Bien sûr, merci !

Je vous ai ajouté trois comme collaborateurs au Repo . Et vous pouvez rejoindre la chaîne #apollo-link-offline dans Apollo Slack

Bien sûr, tout le monde est invité à collaborer sur le GitHub Repo et à discuter sur Slack :)
Je suis très excité

Pour ceux d'entre vous qui arrivent ici depuis une recherche Google, j'ai rédigé un résumé de toutes les technologies hors ligne existantes disponibles aujourd'hui pour Apollo Client 2.0.

https://github.com/benseitz/apollo-link-offline/issues/1#issuecomment -371678922

Apollo travaille-t-il sur un équivalent AWS AppSync ? Nous avons déjà un serveur GraphQL et avons besoin d'une mise en cache client hors ligne pour les requêtes et les mutations à l'aide de notre serveur, et non des solutions AWS (c'est-à-dire Lamda, DynamoDB, Elastic Search).

espérons que cela fonctionne.

@masull Ce serait absolument incroyable. J'ai essayé et je ne pensais pas que firebase ou parse platform fonctionnait pour moi, donc avoir cette fonctionnalité serait glorieux.
Avoir du mal à tout mettre en place avec plein de packages... pas marrant du tout :)

Cette page vous a été utile?
0 / 5 - 0 notes