diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/README.md b/README.md
index 2f0f67d..bfcaac9 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,487 @@
-# vue-apollo
-Vue apollo integration
+# Apollo in Vue
+
+Integrates [apollo](http://www.apollostack.com/) in your vue components with declarative queries.
+
+## Installation
+
+
+ npm install vue-apollo
+
+## Usage
+
+### Configuration
+
+```javascript
+import Vue from 'vue';
+import ApolloClient from 'apollo-client';
+import VueApollo from 'vue-apollo'
+
+const apolloClient = new ApolloClient({
+ /* ... */
+});
+
+Vue.use(VueApollo, {
+ apolloClient,
+});
+```
+
+### Usage in components
+
+To declare apollo queries in your Vue component, add an `apollo` object :
+
+```javascript
+new Vue({
+ apollo: {
+ // Apollo specific options
+ }
+});
+```
+
+You can access the [apollo-client](http://docs.apollostack.com/apollo-client/index.html) instance with `this.$apollo.client` in all your vue components.
+
+### Queries
+
+In the `data` object, add an attribute for each property you want to feed with the result of an Apollo query.
+
+#### Simple query
+
+Put the [gql](http://docs.apollostack.com/apollo-client/core.html#gql) query directly as the value:
+
+```javascript
+apollo: {
+ // Non-reactive query
+ data: {
+ // Simple query that will update the 'hello' vue property
+ hello: gql`{hello}`
+ }
+}
+```
+
+Don't forget to initialize your property in your vue component:
+
+```javascript
+data () {
+ return {
+ // Initialize your apollo data
+ hello: ''
+ }
+}
+```
+
+Server-side, add the corresponding schema and resolver:
+
+```javascript
+export const schema = `
+type Query {
+ hello: String
+}
+
+schema {
+ query: Query
+}
+`;
+
+export const resolvers = {
+ Query: {
+ hello(root, args, context) {
+ return "Hello world!";
+ }
+ }
+};
+```
+
+For more info, visit the [apollo doc](http://docs.apollostack.com/apollo-server/index.html).
+
+You can then use your property as usual in your vue component:
+
+```html
+
+
+
Hello
+
+ {{hello}}
+
+
+
+```
+
+#### Query with parameters
+
+You can add variables (read parameters) to your `gql` query by declaring `query` and `variables` in an object:
+
+```javascript
+// Apollo-specific options
+apollo: {
+ // Non-reactive query
+ data: {
+ // Query with parameters
+ ping: {
+ // gql query
+ query: gql`query PingMessage($message: String!) {
+ ping(message: $message)
+ }`,
+ // Static parameters
+ variables: {
+ message: 'Meow'
+ }
+ }
+ }
+}
+```
+
+You can use the following apollo options in the object:
+ - `forceFetch`
+ - `fragments`
+
+See the [apollo doc](http://docs.apollostack.com/apollo-client/queries.html#query) for more details.
+
+For example, you could add the `forceFetch` apollo option like this:
+
+```javascript
+apollo: {
+ data: {
+ // Query with parameters
+ ping: {
+ query: gql`query PingMessage($message: String!) {
+ ping(message: $message)
+ }`,
+ variables: {
+ message: 'Meow'
+ },
+ // Additional options here
+ forceFetch: true
+ }
+ }
+}
+```
+
+Don't forget to initialize your property in your vue component:
+
+```javascript
+data () {
+ return {
+ // Initialize your apollo data
+ ping: ''
+ }
+}
+```
+
+Server-side, add the corresponding schema and resolver:
+
+```javascript
+export const schema = `
+type Query {
+ ping(message: String!): String
+}
+
+schema {
+ query: Query
+}
+`;
+
+export const resolvers = {
+ Query: {
+ ping(root, { message }, context) {
+ return `Answering ${message}`;
+ }
+ }
+};
+```
+
+And then use it in your vue component:
+
+```html
+
+
+
+```
+
+#### Reactive parameters
+
+Use a function instead to make the parameters reactive with vue properties:
+
+```javascript
+// Apollo-specific options
+apollo: {
+ // Non-reactive query
+ data: {
+ // Query with parameters
+ ping: {
+ query: gql`query PingMessage($message: String!) {
+ ping(message: $message)
+ }`,
+ // Reactive parameters
+ variables() {
+ // Use vue reactive properties here
+ return {
+ message: this.pingInput
+ }
+ }
+ }
+ }
+}
+```
+
+This will re-fetch the query each time a parameter changes, for example:
+
+```html
+
+
+
Ping
+
+
+ {{ping}}
+
+
+
+```
+
+#### Advanced options
+
+These are the available advanced options you can use:
+- `update(data) {return ...}` to customize the value that is set in the vue property, for example if the field names don't match
+- `result(data)` is a hook called when a result is received
+- `error(errors, type)` is a hook called when there are errors, `type` value can either be `'sending'` or `'execution'`
+- `loadingKey` will update the component data property you pass as the value. You should initialize this property to `0` in the component `data()` hook. When the query is loading, this property will be incremented by 1 and as soon as it no longer is, the property will be decremented by 1. That way, the property can represent a counter of currently loading queries.
+- `watchLoading(isLoading, countModifier)` is a hook called when the loading state of the query changes. The `countModifier` parameter is either equal to `1` when the query is now loading, or `-1` when the query is no longer loading.
+
+
+```javascript
+// Apollo-specific options
+apollo: {
+ // Non-reactive query
+ data: {
+ // Advanced query with parameters
+ // The 'variables' method is watched by vue
+ pingMessage: {
+ query: gql`query PingMessage($message: String!) {
+ ping(message: $message)
+ }`,
+ // Reactive parameters
+ variables() {
+ // Use vue reactive properties here
+ return {
+ message: this.pingInput
+ }
+ },
+ // We use a custom update callback because
+ // the field names don't match
+ // By default, the 'pingMessage' attribute
+ // would be used on the 'data' result object
+ // Here we know the result is in the 'ping' attribute
+ // considering the way the apollo server works
+ update(data) {
+ console.log(data);
+ // The returned value will update
+ // the vue property 'pingMessage'
+ return data.ping;
+ },
+ // Optional result hook
+ result(data) {
+ console.log("We got some result!");
+ },
+ // Error handling
+ error(errors, type) {
+ console.error(`We've got ${errors.length} errors of type '${type}'`);
+ },
+ // Loading state
+ // loadingKey is the name of the data property
+ // that will be incremented when the query is loading
+ // and decremented when it no longer is.
+ loadingKey: 'loadingQueriesCount',
+ // watchLoading will be called whenever the loading state changes
+ watchLoading(isLoading, countModifier) {
+ // isLoading is a boolean
+ // countModifier is either 1 or -1
+ }
+ }
+ }
+}
+```
+
+If you use ES2015, you can also write the `update` like this:
+
+```javascript
+update: data => data.ping
+```
+
+### Reactive Queries
+
+*For now, the reactivity in apollo is quite limited, since you can only do polling.*
+
+For more info, see the [apollo doc](http://docs.apollostack.com/apollo-client/core.html#watchQuery).
+
+Add your queries in a `watch` object instead of `data`:
+
+```javascript
+// Apollo-specific options
+apollo: {
+ // Reactive query
+ watch: {
+ // 'tags' data property on vue instance
+ tags: {
+ query: gql`{
+ tags {
+ id,
+ label
+ }
+ }`,
+ pollInterval: 300 // ms
+ }
+ }
+}
+```
+
+You can use the apollo options, for example:
+ - `forceFetch`
+ - `returnPartialData`
+ - `pollInterval`
+ - `fragments`
+
+See the [apollo doc](http://docs.apollostack.com/apollo-client/queries.html#watchQuery) for more details.
+
+You can also use the advanced options detailed above, like `result` or `watchLoading`.
+
+Here is how the server-side looks like:
+
+```javascript
+export const schema = `
+type Tag {
+ id: Int
+ label: String
+}
+
+type Query {
+ tags: [Tag]
+}
+
+schema {
+ query: Query
+}
+`;
+
+// Fake word generator
+import casual from 'casual';
+
+// Let's generate some tags
+var id = 0;
+var tags = [];
+for (let i = 0; i < 42; i++) {
+ addTag(casual.word);
+}
+
+function addTag(label) {
+ let t = {
+ id: id++,
+ label
+ };
+ tags.push(t);
+ return t;
+}
+
+export const resolvers = {
+ Query: {
+ tags(root, args, context) {
+ return tags;
+ }
+ }
+};
+```
+
+### Mutations
+
+Mutations are queries that changes your data state on your apollo server. For more info, visit the [apollo doc](http://docs.apollostack.com/apollo-client/core.html#Mutations).
+
+```javascript
+methods: {
+ addTag() {
+ // Mutate the tags data
+ // You can also use this.$apollo.client.mutate
+ this.$apollo.mutate({
+ mutation: gql`mutation AddTag($label: String!) {
+ addTag(label: $label) {
+ id,
+ label
+ }
+ }`,
+ // Parameters
+ variables: {
+ label: this.tagLabel
+ }
+ }).then((data) => {
+ // Result
+ console.log(data);
+ this.tagLabel = '';
+ }).catch((error) => {
+ // Error
+ console.error(error);
+ });
+ }
+}
+```
+
+Server-side:
+
+```javascript
+export const schema = `
+type Tag {
+ id: Int
+ label: String
+}
+
+type Query {
+ tags: [Tag]
+}
+
+type Mutation {
+ addTag(label: String!): Tag
+}
+
+schema {
+ query: Query
+ mutation: Mutation
+}
+`;
+
+// Fake word generator
+import faker from 'faker';
+
+// Let's generate some tags
+var id = 0;
+var tags = [];
+for (let i = 0; i < 42; i++) {
+ addTag(faker.random.word());
+}
+
+function addTag(label) {
+ let t = {
+ id: id++,
+ label
+ };
+ tags.push(t);
+ return t;
+}
+
+export const resolvers = {
+ Query: {
+ tags(root, args, context) {
+ return tags;
+ }
+ },
+ Mutation: {
+ addTag(root, { label }, context) {
+ console.log(`adding tag '${label}'`);
+ return addTag(label);
+ }
+ }
+};
+```
+
+---
+
+LICENCE ISC - Created by Guillaume CHAU (@Akryum)
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..2f764dd
--- /dev/null
+++ b/index.js
@@ -0,0 +1,2 @@
+import VueApollo from './lib/vue-plugin';
+export default VueApollo;
diff --git a/lib/vue-plugin.js b/lib/vue-plugin.js
new file mode 100644
index 0000000..a048704
--- /dev/null
+++ b/lib/vue-plugin.js
@@ -0,0 +1,230 @@
+import _ from 'lodash';
+
+let apolloClient = null;
+
+class DollarApollo {
+ constructor(vm) {
+ this.vm = vm;
+ this.querySubscriptions = {};
+ }
+
+ get client() {
+ return apolloClient;
+ }
+
+ get query() {
+ return this.client.query;
+ }
+
+ watchQuery(options) {
+ const vm = this.vm;
+ const observable = this.client.watchQuery(options);
+ const _subscribe = observable.subscribe.bind(observable);
+ observable.subscribe = (function(options) {
+ let sub = _subscribe(options);
+ vm._apolloSubscriptions.push(sub);
+ return sub;
+ }).bind(observable);
+ return observable;
+ }
+
+ get mutate() {
+ return this.client.mutate;
+ }
+
+ option(key, options, watch) {
+ const vm = this.vm;
+ const $apollo = this;
+
+ let query, observer, sub;
+ let simpleQuery = false;
+
+ let firstLoadingDone = false;
+
+ let loadingKey = options.loadingKey;
+ let loadingChangeCb = options.watchLoading;
+
+ if (options.pollInterval) {
+ watch = true;
+ }
+
+ if (typeof loadingChangeCb === 'function') {
+ loadingChangeCb = loadingChangeCb.bind(vm);
+ }
+
+ // Simple query
+ if (!options.query) {
+ query = options;
+ simpleQuery = true;
+ }
+
+ function generateApolloOptions(variables) {
+ const apolloOptions = _.omit(options, [
+ 'variables',
+ 'watch',
+ 'update',
+ 'result',
+ 'error',
+ 'loadingKey',
+ 'watchLoading',
+ ]);
+ apolloOptions.variables = variables;
+ return apolloOptions;
+ }
+
+ function q(variables) {
+ applyLoadingModifier(1);
+
+ if (simpleQuery) {
+
+ $apollo.query({
+ query
+ }).then(nextResult).catch(catchError);
+
+ } else if (watch) {
+
+ if (options.forceFetch && observer) {
+ // Refresh query
+ observer.refetch(variables, {
+ forceFetch: !!options.forceFetch
+ });
+ } else {
+ if (sub) {
+ sub.unsubscribe();
+ }
+
+ // Create observer
+ observer = $apollo.watchQuery(generateApolloOptions(variables));
+
+ // Create subscription
+ sub = observer.subscribe({
+ next: nextResult,
+ error: catchError
+ });
+ }
+
+ } else {
+
+ $apollo.query(generateApolloOptions(variables)).then(nextResult).catch(catchError);
+
+ }
+ }
+
+ if (typeof options.variables === 'function') {
+ vm.$watch(options.variables.bind(vm), q, {
+ immediate: true
+ });
+ } else {
+ q(options.variables);
+ }
+
+ function nextResult({ data }) {
+ applyData(data);
+ }
+
+ function applyData(data) {
+ loadingDone();
+
+ console.log(data, key);
+
+ if (typeof options.update === 'function') {
+ vm[key] = options.update.call(vm, data);
+ } else if (data[key] === undefined) {
+ console.error(`Missing ${key} attribute on result`, data);
+ } else {
+ vm[key] = data[key];
+ }
+
+ if (typeof options.result === 'function') {
+ options.result.call(vm, data);
+ }
+ }
+
+ function applyLoadingModifier(value) {
+ if (loadingKey) {
+ vm.$set(loadingKey, vm.$get(loadingKey) + value);
+ }
+
+ if (loadingChangeCb) {
+ loadingChangeCb(value === 1, value);
+ }
+ }
+
+ function loadingDone() {
+ if (!firstLoadingDone) {
+ applyLoadingModifier(-1);
+ firstLoadingDone = true;
+ }
+ }
+
+ function catchError(error) {
+ loadingDone();
+
+ if (error.graphQLErrors && error.graphQLErrors.length !== 0) {
+ console.error(`GraphQL execution errors for query ${query}`);
+ for (let e of error.graphQLErrors) {
+ console.error(e);
+ }
+ } else if (error.networkError) {
+ console.error(`Error sending the query ${query}`, error.networkError);
+ } else {
+ console.error(error);
+ }
+
+ if (typeof options.error === 'function') {
+ options.error(error);
+ }
+ }
+ }
+}
+
+function prepare() {
+ this._apolloSubscriptions = [];
+
+ this.$apollo = new DollarApollo(this);
+
+ let apollo = this.$options.apollo;
+
+ if (apollo) {
+ // One-time queries with $query(), called each time a Vue dependency is updated (using $watch)
+ if (apollo.data) {
+ for (let key in apollo.data) {
+ this.$apollo.option(key, apollo.data[key], false);
+ }
+ }
+
+ // Auto updating queries with $watchQuery(), re-called each time a Vue dependency is updated (using $watch)
+ if (apollo.watch) {
+ for (let key in apollo.watch) {
+ this.$apollo.option(key, apollo.watch[key], true);
+ }
+ }
+ }
+}
+
+export default {
+ install(Vue, options) {
+
+ apolloClient = options.apolloClient;
+
+ Vue.mixin({
+
+ // Vue 1.x
+ beforeCompile: prepare,
+ // Vue 2.x
+ beforeCreate: prepare,
+
+ destroyed: function() {
+ this._apolloSubscriptions.forEach((sub) => {
+ sub.unsubscribe();
+ });
+ this._apolloSubscriptions = null;
+ if (this.$apollo) {
+ this.$apollo = null;
+ }
+ }
+
+ });
+
+ }
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1e76461
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "vue-apollo",
+ "version": "1.0.0",
+ "description": "Vue apollo integration",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Akryum/vue-apollo.git"
+ },
+ "keywords": [
+ "vue",
+ "apollo",
+ "graphql"
+ ],
+ "author": "Guillaume Chau ",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/Akryum/vue-apollo/issues"
+ },
+ "homepage": "https://github.com/Akryum/vue-apollo#readme",
+ "dependencies": {
+ "lodash": "^4.15.0"
+ }
+}