[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", { \"loose\": true }],\n    \"stage-2\",\n    \"react\"\n  ],\n  \"plugins\": [\"dev-expression\", \"add-module-exports\"]\n}\n"
  },
  {
    "path": ".eslintignore",
    "content": "/examples/*/node_modules/*\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": \"airbnb\",\n  \"env\": {\n    \"jest\": true,\n    \"jasmine\": true\n  },\n  \"parser\": \"babel-eslint\",\n  \"plugins\": [\n    \"flowtype\"\n  ],\n  \"rules\": {\n    \"no-underscore-dangle\": 0,\n    \"flowtype/define-flow-type\": 2\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Custom\nlib/\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\n\nlanguage: node_js\nnode_js:\n  - stable\n\ncache: yarn\n\nbranches:\n  only:\n    - master\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Edvin Erikson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Relay Subscriptions [![npm][npm-badge]][npm]\n\nSubscription support for [Relay Classic](http://facebook.github.io/relay/).\n\n![PoC](http://g.recordit.co/zZfGNmYJTr.gif)\n\n[![Discord][discord-badge]][discord]\n\n## Documentation\n\n- [Guide](#guide)\n- [TodoMVC example](examples/todo)\n- [API reference](docs/API.md)\n\n## Guide\n\n### Installation\n\n```sh\n$ npm i -S react react-relay babel-relay-plugin\n$ npm i -S relay-subscriptions\n```\n\n### Network layer ([API](docs/API.md#network-layer))\n\nTo use Relay Subscriptions, you need to provide a network layer with subscription support. This network layer needs to implement a `sendSubscription` method that takes a subscription request, calls the observer methods on the request when the subscription updates, and returns a disposable for tearing down the subscription.\n\nA simple network layer that uses [Socket.IO](http://socket.io/) as the underlying transport looks like:\n\n```js\nimport Relay from 'react-relay/classic';\nimport io from 'socket.io-client';\n\nexport default class NetworkLayer extends Relay.DefaultNetworkLayer {\n  constructor(...args) {\n    super(...args);\n\n    this.socket = io();\n    this.requests = Object.create(null);\n\n    this.socket.on('subscription update', ({ id, data, errors }) => {\n      const request = this.requests[id];\n      if (errors) {\n        request.onError(errors);\n      } else {\n        request.onNext(data);\n      }\n    });\n  }\n\n  sendSubscription(request) {\n    const id = request.getClientSubscriptionId();\n    this.requests[id] = request;\n\n    this.socket.emit('subscribe', {\n      id,\n      query: request.getQueryString(),\n      variables: request.getVariables(),\n    });\n\n    return {\n      dispose: () => {\n        this.socket.emit('unsubscribe', id);\n      },\n    };\n  }\n}\n```\n\nFor a full example, see [the network layer](examples/todo/js/NetworkLayer.js) in the TodoMVC example.\n\nIf your server uses [GraphQL.js](https://github.com/graphql/graphql-js), [graphql-relay-subscription](https://github.com/taion/graphql-relay-subscription) provides helpers for implementing subscriptions. For a basic example, see [the server](examples/todo/server.js) and [the schema](examples/todo/data/schema.js) in the TodoMVC example.\n\n### Environment ([API](docs/API.md#relaysubscriptionsenvironment))\n\nInstead of using a standard `Relay.Environment`, use a `RelaySubscriptions.Environment`. This environment class adds subscription support to the standard Relay environment.\n\n```js\nimport RelaySubscriptions from 'relay-subscriptions';\n\nimport NetworkLayer from './NetworkLayer';\n\nconst environment = new RelaySubscriptions.Environment();\nenvironment.injectNetworkLayer(new NetworkLayer());\n```\n\n### Subscriptions ([API](docs/API.md#subscription))\n\nSubclass the `Subscription` class to define subscriptions. This base class is similar to `Relay.Mutation`. A basic subscription looks like:\n\n```js\nimport Relay from 'react-relay/classic';\nimport { Subscription } from 'relay-subscriptions';\n\nimport Widget from '../components/Widget';\n\nexport default class WidgetSubscription extends Subscription {\n  static fragments = {\n    widget: () => Relay.QL`\n      fragment on Widget {\n        id\n      }\n    `,\n  };\n\n  getSubscription() {\n    return Relay.QL`\n      subscription {\n        updateWidget(input: $input) {\n          widget {\n            ${Widget.getFragment('widget')}\n          }\n        }\n      }\n    `;\n  }\n\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        widget: this.props.widget.id,\n      },\n    }];\n  }\n\n  getVariables() {\n    return {\n      id: this.props.widget.id,\n    };\n  }\n}\n```\n\nDue to an open issue ([#12]), for a `RANGE_ADD` subscription, you must manually request the `__typename` field on the edge in the payload.\n\nFor full examples, see [the subscriptions](examples/todo/js/subscriptions) in the TodoMVC example.\n\n### Containers ([API](docs/API.md#relaysubscriptionscreatecontainer))\n\nFor components with subscriptions, use `RelaySubscriptions.createContainer` instead of `Relay.createContainer`. Define your Relay fragments normally, including the fragments for any subscriptions you need, then define a `subscriptions` array of functions that create the desired subscriptions from the component's props.\n\n```js\nimport React from 'react';\nimport Relay from 'react-relay/classic';\nimport RelaySubscriptions from 'relay-subscriptions';\n\nimport WidgetSubscription from '../subscriptions/WidgetSubscription';\n\nclass Widget extends React.Component { /* ... */ }\n\nexport default RelaySubscriptions.createContainer(Widget, {\n  fragments: {\n    widget: () => Relay.QL`\n      fragment on Widget {\n        # ...\n        ${WidgetSubscription.getFragment('widget')}\n      }\n    `,\n  },\n\n  subscriptions: [\n    ({ widget }) => new WidgetSubscription({ widget }),\n  ],\n})\n```\n\nIf you want to manually manage your subscription, the container also adds a `subscribe` method on `props.relay`, which takes a `Subscription` and an optional observer, and returns a disposable for tearing down the subscription.\n\n## TODO\n\n- [ ] Add tests ([#1])\n- [ ] Automatically add `__typename` to query for `RANGE_ADD` subscriptions ([#12])\n\n## Credits\nBig thanks to [@taion](https://github.com/taion) for cleaning up my mess, creating a really nice API and these amazing docs :tada: \n\n[#1]: https://github.com/edvinerikson/relay-subscriptions/issues/1\n[#12]: https://github.com/edvinerikson/relay-subscriptions/issues/12\n\n[npm-badge]: https://img.shields.io/npm/v/relay-subscriptions.svg\n[npm]: https://www.npmjs.org/package/relay-subscriptions\n\n[discord-badge]: https://img.shields.io/badge/Discord-join%20chat%20%E2%86%92-738bd7.svg\n[discord]: https://discord.gg/0ZcbPKXt5bX40xsQ\n"
  },
  {
    "path": "docs/API/Subscription.md",
    "content": "# Subscription\nRelaySubscriptions makes use of the `Relay.Mutation` API.\nIf you are familiar with the mutation api this shouldn't be any new things.\nExcept the `getSubscription` method which replaced `getMutation`.\n\n# Overview\n\n### Properties\n`static fragments`  \n_Declare this subscription's data dependencies here_  \n`static initialVariables`  \n_A default set of variables to make available to this subscription's fragment builders_  \n`static prepareVariables`  \n_A method to modify the variables based on the runtime environment, previous variables, or the meta route_\n\n### Methods\n`constructor(props)`  \n`abstract getConfigs()`  \n`abstract getSubscription()`  \n`abstract getVariables()`  \n`static getFragment(fragmentName[, variableMapping])`  \n\n# Properties\n## fragments (static property)\n```js\nstatic fragments: RelayMutationFragments<$Keys<Tp>>\n\n// Type of RelayMutationFragments\ntype RelayMutationFragments<Tk> = {\n  [key: Tk]: FragmentBuilder;\n};\n\n// Type of FragmentBuilder\ntype FragmentBuilder = (variables: Variables) => RelayConcreteNode;\n```\nWe declare our subscription' data dependencies here, just as we would with a container.\n\n### Example\n```js\nclass UpdateTodoSubscription extends RelaySubscriptions.Subscription {\n  static fragments = {\n    todo: () => Relay.QL`\n      fragment on Todo {\n        id\n        text\n        complete\n      }\n    `,\n  };\n}\n```\n\n## initialVariables (static property)\n`static initialVariables: {[name: string]: mixed};`  \nThe defaults we specify here will become available to our fragment builders:\n### Example\n```js\nclass AddTodoSubscription extends RelaySubscriptions.Subscription {\n  static initialVariables = {orderby: 'priority'};\n  static fragments = {\n    todos: () => Relay.QL`\n      # The variable defined above is available here as $orderby\n      fragment on Viewer { todos(orderby: $orderby) { ... } }\n    `,\n  };\n  /* ... */\n}\n```\n\n## prepareVariables (static property)\n```js\nstatic prepareVariables: ?(\n  prevVariables: {[name: string]: mixed},\n  route: RelayMetaRoute,\n) => {[name: string]: mixed}\n\n// Type of `route` argument\ntype RelayMetaRoute = {\n  name: string;\n}\n```\nIf we provide to a subscription a method that conforms to the signature described above, it will be given the opportunity to modify the fragment builders' variables, based on the previous variables (or the initialVariables if no previous ones exist), the meta route, and the runtime environment. Whatever variables this method returns will become available to this subscription's fragment builders.\n### Example\n```js\nclass BuySongSubscription extends RelaySubscriptions.Subscription {\n  static initialVariables = {format: 'mp3'};\n  static prepareVariables = (prevVariables) => {\n    var overrideVariables = {};\n    var formatPreference = localStorage.getItem('formatPreference');\n    if (formatPreference) {\n      overrideVariables.format = formatPreference;  // Lossless, hopefully\n    }\n    return {...prevVariables, overrideVariables};\n  };\n  /* ... */\n}\n```\n\n# Methods\n## constructor\n\nCreate a subscription instance using the `new` keyword, optionally passing it some props. Note that `this.props` is not available inside the constructor function, but are set for all the methods mentioned below (getConfigs, getVariables, etc). This restriction is due to the fact that subscription props may depend on data from the RelayEnvironment, which isn't known until the subscription is applied with `subscribe` provided by `SubscriptionProvider` and `SubscriptionContainer`.\n\n### Example\n```js\nconst flightsUpdateSub = new FlightsUpdateSubscription({airport: 'yvr'});\nthis.props.subscriptions.subscribe(flightsUpdateSub);\n```\n\n## getConfigs (abstract method)\n`abstract getConfigs(): Array<{[key: string]: mixed}>`  \nImplement this required method to give Relay instructions on how to use the response payload from each subscription to update the client-side store.\n\n### Example\n```js\nclass LikeStorySubscription extends Subscription {\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        story: this.props.story.id,\n      },\n    }];\n  }\n}\n```\n\n## getSubscription (abstract method)\n`abstract getSubscription(): GraphQL.Subscription`  \nImplement this required method to return a GraphQL subscription operation that represents the subscription to subscribe to.\n\n### Example\n```js\nclass LikeStorySubscription extends Subscription {\n  getSubscription() {\n    return Relay.QL`subscription {\n      likeStorySubscribe {\n        story {\n          likes {\n            likeSentence\n            count\n          }\n        }\n      }\n    }`;\n  }\n}\n```\n\n## getVariables (abstract method)\n`abstract getVariables(): {[name: string]: mixed}`  \nImplement this required method to prepare variables to be used as input to the subscription.\n\n## Example\n```js\nclass DestroyShipSubscription extends RelaySubscriptions.Subscription {\n  getVariables() {\n    return {\n      factionId: this.props.faction.id,\n    };\n  }\n}\n```\n\n## getFragment (static method)\n```js\nstatic getFragment(\n  fragmentName: $Keys<Tp>,\n  variableMapping?: Variables\n): RelayFragmentReference\n\n// Type of the variableMapping argument\ntype Variables = {[name: string]: mixed};\n```\nGets a fragment reference for use in a parent's query fragment.\n\n### Example\n```js\nclass StoryComponent extends React.Component {\n  /* ... */\n  static fragments = {\n    story: () => Relay.QL`\n      fragment on Story {\n        id,\n        text,\n        ${LikeStorySubscription.getFragment('story')},\n      }\n    `,\n  };\n}\n```\nYou can also pass variables to the subscription's fragment builder from the outer fragment that contains it.\n```js\nclass Movie extends React.Component {\n  /* ... */\n  static fragments = {\n    movie: () => Relay.QL`\n      fragment on Movie {\n        posterImage(lang: $lang) { url },\n        trailerVideo(format: $format, lang: $lang) { url },\n        ${MovieUpdateSubscription.getFragment('movie', {\n          format: variables.format,\n          lang: variables.lang,\n        })},\n      }\n    `,\n  };\n}\n```\n"
  },
  {
    "path": "docs/API.md",
    "content": "# API Reference\n\n- [Network layer](#network-layer)\n- [RelaySubscriptions.Environment](#relaysubscriptionsenvironment)\n- [Subscription](#subscription)\n- [RelaySubscriptions.createContainer](#relaysubscriptionscreatecontainer)\n\n## Network layer\n\nYou must implement a network layer that connects to a backend with subscription support. This network layer must implement the following additional method:\n\n```js\nsendSubscription: (request: SubscriptionRequest) => Disposable\n```\n\nThe `SubscriptionRequest` object supports:\n\n```js\ntype SubscriptionRequest {\n  getQueryString: () => string;\n  getVariables: () => Variables;\n  getClientSubscriptionId: () => string;\n\n  onNext: (payload: SubscriptionResult) => void;\n  onError: (error: any) => void;\n  onCompleted: (value: any) => void;\n\n  getDebugName: () => string;\n}\n```\n\nThe `getQueryString` method returns the GraphQL query string. The `getVariables` method returns the variables for the query. The `getClientSubscriptionId` method returns a client-side ID for the subscription.\n\nCall the `onNext`, `onError`, and `onCompleted` methods when the subscription updates.\n\nThe return value is expected to conform to:\n\n```js\ntype Disposable = {\n  dispose: () => void;\n}\n```\n\nThe `dispose` method should tear down the subscription.\n\n## `RelaySubscriptions.Environment`\n\n`RelaySubscriptions.Environment` extends `Relay.Environment` and provides subscription support.\n\n### `subscribe`\n\nThis method has the signature:\n\n```js\nsubscribe: (subscription: Subscription, observer?: Observer) => Disposable\n```\n\nThis method will make the subscription.\n\nThe observer, if provided, is expected to conform to:\n\n```js\ntype Observer = {\n  onNext?: (value: SubscriptionResult) => void;\n  onError?: (error: any) => void;\n  onCompleted?: (value: any) => void;\n}\n```\n\nThe specified callbacks will be invoked when the subscription updates. The `onNext` callback fires after the store update.\n\n## `Subscription`\n\nSubclass the `Subscription` class to define a subscription. This base class is similar to `Relay.Mutation`, except that you need to implement `getSubscription` instead of `getMutation` and `getFatQuery`.\n\n```js\nimport { Subscription } from 'relay-subscriptions';\n\nexport default class WidgetSubscription extends Subscription {\n  /* ... */\n}\n```\n\n### `constructor`\n\nAs with `Relay.Mutation`, you can construct an instance of a subclass of `Subscription` with the `new` keyword and optional props.\n\n```js\nnew WidgetSubscription({ widget })\n```\n\n### Static properties\n\nDefine these properties to specify the input data dependencies for the subscription.\n\n#### `fragments`\n\nThis static property defines the subscription's data requirements as a object of fragment builders, as with the `fragments` static property on `Relay.Mutation`. These fragments can then be composed elsewhere with `MySubscription.getFragment(fragmentName)`.\n\n```js\nstatic fragments = {\n  widget: () => Relay.QL`\n    fragment on Widget {\n      id\n    }\n  },\n};\n```\n\n#### `initialVariables`\n\nIf provided, this specifies the default variables for the fragment builders, as with the `initialVariables` static property on `Relay.Mutation`.\n\n#### `prepareVariables`\n\nIf provided, this method modifies variables for the fragment builders, as with the `prepareVariables` static method on `Relay.Mutation`.\n\n### Abstract methods\n\nImplement these methods to define the subscription's behavior.\n\n#### `getSubscription`\n\nThis method should return the concrete subscription query. The query should use the `$input` variable for the subscription input. Unlike with mutations, this is not a fat query, so it must specify all desired fields. You can compose in fragments from container components here, which can help manage code duplication.\n\n```js\ngetSubscription() {\n  return Relay.QL`\n    subscription {\n      updateWidget(input: $input) {\n        ${Widget.getFragment('widget')}\n      }\n    }\n  `;\n}\n```\n\n#### `getConfigs`\n\nThis method should return the mutation configs, as with the `getConfigs` method on `Relay.Mutation`.\n\n```js\ngetConfigs() {\n  return [{\n    type: 'FIELDS_CHANGE',\n    fieldIDs: {\n      widget: this.props.widget.id,\n    },\n  }];\n}\n```\n\n#### `getVariables`\n\nThis method should return the subscription input variables, as with the `getVariables` method on `Relay.Mutation`.\n\n```js\ngetVariables() {\n  return {\n    id: this.props.widget.id,\n  };\n}\n```\n\n## `RelaySubscriptions.createContainer`\n\n`RelaySubscriptions.createContainer` behaves like `Relay.createContainer`. It provides additional functionality for subscription support.\n\n### Container specification\n\n#### `subscriptions`\n\nThe specification for a Relay Subscriptions container accepts an optional `subscriptions` property:\n\n```js\nsubscriptions?: subscriptionFn[]\n```\n\nThese subscription functions are expected to have the signature:\n\n```js\ntype subscriptionFn = (props: Object) => ?Subscription<any>;\n```\n\nThis function can return a falsy value to indicate that no subscription is desired.\n\nThe Relay Subscriptions container will manage these subscriptions. It will establish the subscription after the component mounts, replace any subscriptions that have changed type or variables, and tear down these subscriptions when the component unmounts.\n\n```js\nimport RelaySubscriptions from 'relay-subscriptions';\n\n/* ... */\n\nexport default RelaySubscriptions.createContainer(Widget, {\n  fragments: {\n    widget: () => Relay.QL`\n      fragment on Widget {\n        # ...\n        ${WidgetSubscription.getFragment('widget')}\n      }\n    `,\n  },\n\n  subscriptions: [\n    ({ pending, widget }) => !pending && new WidgetSubscription({ widget }),\n  ],\n});\n```\n\n### `props.relay`\n\nThe Relay Subscriptions container injects an augmented `props.relay` to the component with subscription functionality.\n\n#### `subscribe`\n\nThis method invokes the `subscribe` method on the Relay Subscriptions environment. It has the same signature of:\n\n```js\nsubscribe: (subscription: Subscription, observer?: Observer) => Disposable\n```\n\nYou can use this to manually manage the subscription.\n\n```js\nimport RelaySubscriptions from 'relay-subscriptions';\n\n/* ... */\n\nclass Widget extends React.Component {\n  componentDidMount() {\n    const { relay, widget } = this.props;\n    this.subscription = relay.subscribe(\n      new WidgetSubscription({ widget }),\n    );\n  }\n\n  componentWillUnmount() {\n    this.subscription.dispose();\n  }\n\n  /* ... */\n}\n\nexport default RelaySubscriptions.createContainer(Widget, {\n  fragments: {\n    widget: () => Relay.QL`\n      fragment on Widget {\n        # ...\n        ${WidgetSubscription.getFragment('widget')}\n      }\n    `,\n  },\n});\n```\n"
  },
  {
    "path": "examples/.eslintrc",
    "content": "{\n  \"rules\": {\n    // Don't fail linting if example dependencies aren't installed.\n    \"import/no-unresolved\": \"off\"\n  }\n}\n"
  },
  {
    "path": "examples/todo/.babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", { \"loose\": true }],\n    \"stage-2\",\n    \"react\"\n  ],\n  \"plugins\": [\n    [\"relay\", { \"schema\": \"data/schema.graphql\" }]\n  ]\n}\n"
  },
  {
    "path": "examples/todo/README.md",
    "content": "# Relay TodoMVC\n\n## Installation\n\n```\nnpm install\n```\n\n## Running\n\nStart a local server:\n\n```\nnpm start\n```\n\n## Developing\n\nAny changes you make to files in the `js/` directory will cause the server to\nautomatically rebuild the app and refresh your browser.\n\nIf at any time you make changes to `data/schema.js`, stop the server,\nregenerate `data/schema.json`, and restart the server:\n\n```\nnpm run update-schema\nnpm start\n```\n\n## License\n\n    This file provided by Facebook is for non-commercial testing and evaluation\n    purposes only.  Facebook reserves all rights not expressly granted.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n    FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "examples/todo/data/database.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nexport class Todo {}\nexport class User {}\n\n// Mock authenticated ID\nexport const VIEWER_ID = 'me';\n\n// Mock user data\nconst viewer = new User();\nviewer.id = VIEWER_ID;\nconst usersById = {\n  [VIEWER_ID]: viewer,\n};\n\n// Mock todo data\nconst todosById = {};\nconst todoIdsByUser = {\n  [VIEWER_ID]: [],\n};\n\nconst notifiers = [];\n\nfunction notifyChange(topic, data) {\n  // Delay the change notification to avoid the subscription update hitting the\n  // client before the mutation response.\n  setTimeout(() => {\n    notifiers.forEach(notifier => notifier({ topic, data }));\n  }, 100);\n}\n\nexport function addNotifier(cb) {\n  notifiers.push(cb);\n\n  return () => {\n    const index = notifiers.indexOf(cb);\n    if (index !== -1) {\n      notifiers.splice(index, 1);\n    }\n  };\n}\n\nlet nextTodoId = 0;\n\nexport function addTodo(text, complete) {\n  const todo = new Todo();\n  todo.complete = !!complete;\n  todo.id = `${nextTodoId++}`;\n  todo.text = text;\n  todosById[todo.id] = todo;\n  todoIdsByUser[VIEWER_ID].push(todo.id);\n  notifyChange('add_todo', todo);\n  return todo.id;\n}\n\naddTodo('Taste JavaScript', true);\naddTodo('Buy a unicorn', false);\n\nexport function getTodo(id) {\n  return todosById[id];\n}\n\nexport function getTodos(status = 'any') {\n  const todos = todoIdsByUser[VIEWER_ID].map(id => todosById[id]);\n  if (status === 'any') {\n    return todos;\n  }\n  return todos.filter(todo => todo.complete === (status === 'completed'));\n}\n\nexport function changeTodoStatus(id, complete) {\n  const todo = getTodo(id);\n  todo.complete = complete;\n  notifyChange(`update_todo_${id}`, todo);\n}\n\nexport function getUser(id) {\n  return usersById[id];\n}\n\nexport function getViewer() {\n  return getUser(VIEWER_ID);\n}\n\nexport function markAllTodos(complete) {\n  const changedTodos = [];\n  getTodos().forEach(todo => {\n    if (todo.complete !== complete) {\n      todo.complete = complete; // eslint-disable-line no-param-reassign\n      changedTodos.push(todo);\n      notifyChange(`update_todo_${todo.id}`, todo);\n    }\n  });\n  return changedTodos.map(todo => todo.id);\n}\n\nexport function removeTodo(id) {\n  const todoIndex = todoIdsByUser[VIEWER_ID].indexOf(id);\n  if (todoIndex !== -1) {\n    todoIdsByUser[VIEWER_ID].splice(todoIndex, 1);\n  }\n  notifyChange('delete_todo', { id });\n  delete todosById[id];\n}\n\nexport function removeCompletedTodos() {\n  const todosToRemove = getTodos().filter(todo => todo.complete);\n  todosToRemove.forEach(todo => removeTodo(todo.id));\n  return todosToRemove.map(todo => todo.id);\n}\n\nexport function renameTodo(id, text) {\n  const todo = getTodo(id);\n  todo.text = text;\n  notifyChange(`update_todo_${id}`, todo);\n}\n"
  },
  {
    "path": "examples/todo/data/schema.graphql",
    "content": "schema {\n  query: Root\n  mutation: Mutation\n  subscription: Subscription\n}\n\ninput AddTodoInput {\n  text: String!\n  clientMutationId: String\n}\n\ntype AddTodoPayload {\n  todoEdge: TodoEdge\n  viewer: User\n  clientMutationId: String\n}\n\ninput AddTodoSubscriptionInput {\n  clientSubscriptionId: String\n}\n\ntype AddTodoSubscriptionPayload {\n  todo: Todo\n  todoEdge: TodoEdge\n  viewer: User\n  clientSubscriptionId: String\n}\n\ninput ChangeTodoStatusInput {\n  complete: Boolean!\n  id: ID!\n  clientMutationId: String\n}\n\ntype ChangeTodoStatusPayload {\n  todo: Todo\n  viewer: User\n  clientMutationId: String\n}\n\ninput MarkAllTodosInput {\n  complete: Boolean!\n  clientMutationId: String\n}\n\ntype MarkAllTodosPayload {\n  changedTodos: [Todo]\n  viewer: User\n  clientMutationId: String\n}\n\ntype Mutation {\n  addTodo(input: AddTodoInput!): AddTodoPayload\n  changeTodoStatus(input: ChangeTodoStatusInput!): ChangeTodoStatusPayload\n  markAllTodos(input: MarkAllTodosInput!): MarkAllTodosPayload\n  removeCompletedTodos(input: RemoveCompletedTodosInput!): RemoveCompletedTodosPayload\n  removeTodo(input: RemoveTodoInput!): RemoveTodoPayload\n  renameTodo(input: RenameTodoInput!): RenameTodoPayload\n}\n\n# An object with an ID\ninterface Node {\n  # The id of the object.\n  id: ID!\n}\n\n# Information about pagination in a connection.\ntype PageInfo {\n  # When paginating forwards, are there more items?\n  hasNextPage: Boolean!\n\n  # When paginating backwards, are there more items?\n  hasPreviousPage: Boolean!\n\n  # When paginating backwards, the cursor to continue.\n  startCursor: String\n\n  # When paginating forwards, the cursor to continue.\n  endCursor: String\n}\n\ninput RemoveCompletedTodosInput {\n  clientMutationId: String\n}\n\ntype RemoveCompletedTodosPayload {\n  deletedTodoIds: [String]\n  viewer: User\n  clientMutationId: String\n}\n\ninput RemoveTodoInput {\n  id: ID!\n  clientMutationId: String\n}\n\ntype RemoveTodoPayload {\n  deletedTodoId: ID\n  viewer: User\n  clientMutationId: String\n}\n\ninput RemoveTodoSubscriptionInput {\n  clientSubscriptionId: String\n}\n\ntype RemoveTodoSubscriptionPayload {\n  deletedTodoId: ID\n  viewer: User\n  clientSubscriptionId: String\n}\n\ninput RenameTodoInput {\n  id: ID!\n  text: String!\n  clientMutationId: String\n}\n\ntype RenameTodoPayload {\n  todo: Todo\n  clientMutationId: String\n}\n\ntype Root {\n  viewer: User\n\n  # Fetches an object given its ID\n  node(\n    # The ID of an object\n    id: ID!\n  ): Node\n}\n\ntype Subscription {\n  addTodoSubscription(input: AddTodoSubscriptionInput!): AddTodoSubscriptionPayload\n  removeTodoSubscription(input: RemoveTodoSubscriptionInput!): RemoveTodoSubscriptionPayload\n  updateTodoSubscription(input: UpdateTodoSubscriptionInput!): UpdateTodoSubscriptionPayload\n}\n\ntype Todo implements Node {\n  # The ID of an object\n  id: ID!\n  text: String\n  complete: Boolean\n}\n\n# A connection to a list of items.\ntype TodoConnection {\n  # Information to aid in pagination.\n  pageInfo: PageInfo!\n\n  # A list of edges.\n  edges: [TodoEdge]\n}\n\n# An edge in a connection.\ntype TodoEdge {\n  # The item at the end of the edge\n  node: Todo\n\n  # A cursor for use in pagination\n  cursor: String!\n}\n\ninput UpdateTodoSubscriptionInput {\n  id: ID!\n  clientSubscriptionId: String\n}\n\ntype UpdateTodoSubscriptionPayload {\n  todo: Todo\n  viewer: User\n  clientSubscriptionId: String\n}\n\ntype User implements Node {\n  # The ID of an object\n  id: ID!\n  todos(status: String = \"any\", after: String, first: Int, before: String, last: Int): TodoConnection\n  totalCount: Int\n  completedCount: Int\n}\n"
  },
  {
    "path": "examples/todo/data/schema.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport {\n  GraphQLBoolean,\n  GraphQLID,\n  GraphQLInt,\n  GraphQLList,\n  GraphQLNonNull,\n  GraphQLObjectType,\n  GraphQLSchema,\n  GraphQLString,\n} from 'graphql';\n\nimport {\n  connectionArgs,\n  connectionDefinitions,\n  connectionFromArray,\n  cursorForObjectInConnection,\n  fromGlobalId,\n  globalIdField,\n  mutationWithClientMutationId,\n  nodeDefinitions,\n  toGlobalId,\n} from 'graphql-relay';\n\nimport { subscriptionWithClientId } from 'graphql-relay-subscription';\n\nimport {\n  Todo,\n  User,\n  addTodo,\n  changeTodoStatus,\n  getTodo,\n  getTodos,\n  getUser,\n  getViewer,\n  markAllTodos,\n  removeCompletedTodos,\n  removeTodo,\n  renameTodo,\n} from './database';\n\nconst { nodeInterface, nodeField } = nodeDefinitions(\n  (globalId) => {\n    const { type, id } = fromGlobalId(globalId);\n    if (type === 'Todo') {\n      return getTodo(id);\n    } else if (type === 'User') {\n      return getUser(id);\n    }\n    return null;\n  },\n  (obj) => {\n    /* eslint-disable no-use-before-define */\n    if (obj instanceof Todo) {\n      return GraphQLTodo;\n    } else if (obj instanceof User) {\n      return GraphQLUser;\n    }\n    /* eslint-enable no-use-before-define */\n    return null;\n  }\n);\n\nconst GraphQLTodo = new GraphQLObjectType({\n  name: 'Todo',\n  fields: {\n    id: globalIdField('Todo'),\n    text: {\n      type: GraphQLString,\n      resolve: (obj) => obj.text,\n    },\n    complete: {\n      type: GraphQLBoolean,\n      resolve: (obj) => obj.complete,\n    },\n  },\n  interfaces: [nodeInterface],\n});\n\nconst {\n  connectionType: TodosConnection,\n  edgeType: GraphQLTodoEdge,\n} = connectionDefinitions({\n  name: 'Todo',\n  nodeType: GraphQLTodo,\n});\n\nconst GraphQLUser = new GraphQLObjectType({\n  name: 'User',\n  fields: {\n    id: globalIdField('User'),\n    todos: {\n      type: TodosConnection,\n      args: {\n        status: {\n          type: GraphQLString,\n          defaultValue: 'any',\n        },\n        ...connectionArgs,\n      },\n      resolve: (obj, { status, ...args }) =>\n        connectionFromArray(getTodos(status), args),\n    },\n    totalCount: {\n      type: GraphQLInt,\n      resolve: () => getTodos().length,\n    },\n    completedCount: {\n      type: GraphQLInt,\n      resolve: () => getTodos('completed').length,\n    },\n  },\n  interfaces: [nodeInterface],\n});\n\nconst Root = new GraphQLObjectType({\n  name: 'Root',\n  fields: {\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n    node: nodeField,\n  },\n});\n\nconst GraphQLAddTodoMutation = mutationWithClientMutationId({\n  name: 'AddTodo',\n  inputFields: {\n    text: { type: new GraphQLNonNull(GraphQLString) },\n  },\n  outputFields: {\n    todoEdge: {\n      type: GraphQLTodoEdge,\n      resolve: ({ localTodoId }) => {\n        const todo = getTodo(localTodoId);\n        return {\n          cursor: cursorForObjectInConnection(getTodos(), todo),\n          node: todo,\n        };\n      },\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  mutateAndGetPayload: ({ text }) => {\n    const localTodoId = addTodo(text);\n    return { localTodoId };\n  },\n});\n\nconst GraphQLChangeTodoStatusMutation = mutationWithClientMutationId({\n  name: 'ChangeTodoStatus',\n  inputFields: {\n    complete: { type: new GraphQLNonNull(GraphQLBoolean) },\n    id: { type: new GraphQLNonNull(GraphQLID) },\n  },\n  outputFields: {\n    todo: {\n      type: GraphQLTodo,\n      resolve: ({ localTodoId }) => getTodo(localTodoId),\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  mutateAndGetPayload: ({ id, complete }) => {\n    const localTodoId = fromGlobalId(id).id;\n    changeTodoStatus(localTodoId, complete);\n    return { localTodoId };\n  },\n});\n\nconst GraphQLMarkAllTodosMutation = mutationWithClientMutationId({\n  name: 'MarkAllTodos',\n  inputFields: {\n    complete: { type: new GraphQLNonNull(GraphQLBoolean) },\n  },\n  outputFields: {\n    changedTodos: {\n      type: new GraphQLList(GraphQLTodo),\n      resolve: ({ changedTodoLocalIds }) => changedTodoLocalIds.map(getTodo),\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  mutateAndGetPayload: ({ complete }) => {\n    const changedTodoLocalIds = markAllTodos(complete);\n    return { changedTodoLocalIds };\n  },\n});\n\n// TODO: Support plural deletes\nconst GraphQLRemoveCompletedTodosMutation = mutationWithClientMutationId({\n  name: 'RemoveCompletedTodos',\n  outputFields: {\n    deletedTodoIds: {\n      type: new GraphQLList(GraphQLString),\n      resolve: ({ deletedTodoIds }) => deletedTodoIds,\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  mutateAndGetPayload: () => {\n    const deletedTodoLocalIds = removeCompletedTodos();\n    const deletedTodoIds = deletedTodoLocalIds.map(toGlobalId.bind(null, 'Todo'));\n    return { deletedTodoIds };\n  },\n});\n\nconst GraphQLRemoveTodoMutation = mutationWithClientMutationId({\n  name: 'RemoveTodo',\n  inputFields: {\n    id: { type: new GraphQLNonNull(GraphQLID) },\n  },\n  outputFields: {\n    deletedTodoId: {\n      type: GraphQLID,\n      resolve: ({ id }) => id,\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  mutateAndGetPayload: ({ id }) => {\n    const localTodoId = fromGlobalId(id).id;\n    removeTodo(localTodoId);\n    return { id };\n  },\n});\n\nconst GraphQLRenameTodoMutation = mutationWithClientMutationId({\n  name: 'RenameTodo',\n  inputFields: {\n    id: { type: new GraphQLNonNull(GraphQLID) },\n    text: { type: new GraphQLNonNull(GraphQLString) },\n  },\n  outputFields: {\n    todo: {\n      type: GraphQLTodo,\n      resolve: ({ localTodoId }) => getTodo(localTodoId),\n    },\n  },\n  mutateAndGetPayload: ({ id, text }) => {\n    const localTodoId = fromGlobalId(id).id;\n    renameTodo(localTodoId, text);\n    return { localTodoId };\n  },\n});\n\nconst GraphQLAddTodoSubscription = subscriptionWithClientId({\n  name: 'AddTodoSubscription',\n  outputFields: {\n    todo: {\n      type: GraphQLTodo,\n      resolve: obj => obj,\n    },\n    todoEdge: {\n      type: GraphQLTodoEdge,\n      resolve: obj => ({\n        cursor: cursorForObjectInConnection(getTodos(), getTodo(obj.id)),\n        node: obj,\n      }),\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  subscribe: (input, context) => (\n    context.subscribe('add_todo')\n  ),\n});\n\n\nconst GraphQLRemoveTodoSubscription = subscriptionWithClientId({\n  name: 'RemoveTodoSubscription',\n  outputFields: {\n    deletedTodoId: {\n      type: GraphQLID,\n      resolve: ({ id }) => toGlobalId('Todo', id),\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  subscribe: (input, context) => (\n    context.subscribe('delete_todo')\n  ),\n});\n\nconst GraphQLUpdateTodoSubscription = subscriptionWithClientId({\n  name: 'UpdateTodoSubscription',\n  inputFields: {\n    id: { type: new GraphQLNonNull(GraphQLID) },\n  },\n  outputFields: {\n    todo: {\n      type: GraphQLTodo,\n      resolve: obj => obj,\n    },\n    viewer: {\n      type: GraphQLUser,\n      resolve: () => getViewer(),\n    },\n  },\n  subscribe: ({ id }, context) => (\n    context.subscribe(`update_todo_${fromGlobalId(id).id}`)\n  ),\n});\n\nconst Mutation = new GraphQLObjectType({\n  name: 'Mutation',\n  fields: {\n    addTodo: GraphQLAddTodoMutation,\n    changeTodoStatus: GraphQLChangeTodoStatusMutation,\n    markAllTodos: GraphQLMarkAllTodosMutation,\n    removeCompletedTodos: GraphQLRemoveCompletedTodosMutation,\n    removeTodo: GraphQLRemoveTodoMutation,\n    renameTodo: GraphQLRenameTodoMutation,\n  },\n});\n\nconst Subscription = new GraphQLObjectType({\n  name: 'Subscription',\n  fields: {\n    addTodoSubscription: GraphQLAddTodoSubscription,\n    removeTodoSubscription: GraphQLRemoveTodoSubscription,\n    updateTodoSubscription: GraphQLUpdateTodoSubscription,\n  },\n});\n\nexport const schema = new GraphQLSchema({\n  query: Root,\n  mutation: Mutation,\n  subscription: Subscription,\n});\n"
  },
  {
    "path": "examples/todo/js/NetworkLayer.js",
    "content": "/* eslint-disable no-console */\n\nimport Relay from 'react-relay/classic';\nimport { SubscriptionClient } from 'subscriptions-transport-ws';\n\nexport default class NetworkLayer extends Relay.DefaultNetworkLayer {\n  constructor(...args) {\n    super(...args);\n\n    this._subscriptionClient = new SubscriptionClient(\n      `ws://${global.location.host}/graphql`,\n      { reconnect: true },\n    );\n  }\n\n  sendSubscription(request) {\n    const { unsubscribe } = this._subscriptionClient.request({\n      query: request.getQueryString(),\n      variables: request.getVariables(),\n    }).subscribe({\n      next: ({ errors, data }) => {\n        if (errors) {\n          request.onError(errors);\n        } else {\n          request.onNext(data);\n        }\n      },\n      error: request.onError,\n      complete: request.onCompleted,\n    });\n\n    return { dispose: unsubscribe };\n  }\n\n  disconnect() {\n    this._subscriptionClient.close();\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/app.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport 'todomvc-common';\n\nimport { createHashHistory } from 'history';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {\n  applyRouterMiddleware,\n  IndexRoute,\n  Route,\n  Router,\n  useRouterHistory,\n} from 'react-router';\nimport useRelay from 'react-router-relay';\nimport RelaySubscriptions from 'relay-subscriptions';\n\nimport NetworkLayer from './NetworkLayer';\nimport TodoApp from './components/TodoApp';\nimport TodoList from './components/TodoList';\nimport ViewerQueries from './queries/ViewerQueries';\n\nconst history = useRouterHistory(createHashHistory)({ queryKey: false });\n\nconst environment = new RelaySubscriptions.Environment();\nenvironment.injectNetworkLayer(new NetworkLayer('/graphql'));\n\nconst mountNode = document.getElementById('root');\n\nReactDOM.render(\n  <Router\n    environment={environment}\n    history={history}\n    render={applyRouterMiddleware(useRelay)}\n    forceFetch\n  >\n    <Route\n      path=\"/\"\n      component={TodoApp}\n      queries={ViewerQueries}\n    >\n      <IndexRoute\n        component={TodoList}\n        queries={ViewerQueries}\n        prepareParams={() => ({ status: 'any' })}\n      />\n      <Route\n        path=\":status\"\n        component={TodoList}\n        queries={ViewerQueries}\n      />\n    </Route>\n  </Router>,\n  mountNode\n);\n"
  },
  {
    "path": "examples/todo/js/components/Todo.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport classNames from 'classnames';\nimport React from 'react';\nimport Relay from 'react-relay/classic';\nimport RelaySubscriptions from 'relay-subscriptions';\n\nimport ChangeTodoStatusMutation from '../mutations/ChangeTodoStatusMutation';\nimport RemoveTodoMutation from '../mutations/RemoveTodoMutation';\nimport RenameTodoMutation from '../mutations/RenameTodoMutation';\nimport UpdateTodoSubscription from '../subscriptions/UpdateTodoSubscription';\nimport TodoTextInput from './TodoTextInput';\n\nclass Todo extends React.Component {\n  static propTypes = {\n    viewer: React.PropTypes.object.isRequired,\n    todo: React.PropTypes.object.isRequired,\n    relay: React.PropTypes.object.isRequired,\n  };\n\n  state = {\n    isEditing: false,\n  };\n\n  _handleCompleteChange = (e) => {\n    const { relay, todo, viewer } = this.props;\n    relay.commitUpdate(\n      new ChangeTodoStatusMutation({\n        todo,\n        viewer,\n        complete: e.target.checked,\n      }),\n    );\n  };\n\n  _handleDestroyClick = () => {\n    this._removeTodo();\n  };\n\n  _handleLabelDoubleClick = () => {\n    this._setEditMode(true);\n  };\n\n  _handleTextInputCancel = () => {\n    this._setEditMode(false);\n  };\n\n  _handleTextInputDelete = () => {\n    this._setEditMode(false);\n    this._removeTodo();\n  };\n\n  _handleTextInputSave = (text) => {\n    this._setEditMode(false);\n    const { relay, todo } = this.props;\n    relay.commitUpdate(\n      new RenameTodoMutation({ todo, text }),\n    );\n  };\n\n  _removeTodo() {\n    const { relay, todo, viewer } = this.props;\n    relay.commitUpdate(\n      new RemoveTodoMutation({ todo, viewer }),\n    );\n  }\n\n  _setEditMode = (shouldEdit) => {\n    this.setState({ isEditing: shouldEdit });\n  };\n\n  renderTextInput() {\n    return (\n      <TodoTextInput\n        className=\"edit\"\n        commitOnBlur\n        initialValue={this.props.todo.text}\n        onCancel={this._handleTextInputCancel}\n        onDelete={this._handleTextInputDelete}\n        onSave={this._handleTextInputSave}\n      />\n    );\n  }\n\n  render() {\n    return (\n      <li\n        className={classNames({\n          completed: this.props.todo.complete,\n          editing: this.state.isEditing,\n        })}\n      >\n        <div className=\"view\">\n          <input\n            checked={this.props.todo.complete}\n            className=\"toggle\"\n            onChange={this._handleCompleteChange}\n            type=\"checkbox\"\n          />\n          <label onDoubleClick={this._handleLabelDoubleClick}>\n            {this.props.todo.text}\n          </label>\n          <button\n            className=\"destroy\"\n            onClick={this._handleDestroyClick}\n          />\n        </div>\n        {this.state.isEditing && this.renderTextInput()}\n      </li>\n    );\n  }\n}\n\nexport default RelaySubscriptions.createContainer(Todo, {\n  fragments: {\n    todo: () => Relay.QL`\n      fragment on Todo {\n        id\n        complete\n        text\n        ${ChangeTodoStatusMutation.getFragment('todo')}\n        ${RemoveTodoMutation.getFragment('todo')}\n        ${RenameTodoMutation.getFragment('todo')}\n        ${UpdateTodoSubscription.getFragment('todo')}\n      }\n    `,\n    viewer: () => Relay.QL`\n      fragment on User {\n        ${ChangeTodoStatusMutation.getFragment('viewer')}\n        ${RemoveTodoMutation.getFragment('viewer')}\n      }\n    `,\n  },\n\n  subscriptions: [\n    ({ pending, todo }) => !pending && new UpdateTodoSubscription({ todo }),\n  ],\n});\n"
  },
  {
    "path": "examples/todo/js/components/TodoApp.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport React from 'react';\nimport Relay from 'react-relay/classic';\nimport RelaySubscriptions from 'relay-subscriptions';\n\nimport AddTodoMutation from '../mutations/AddTodoMutation';\nimport AddTodoSubscription from '../subscriptions/AddTodoSubscription';\nimport RemoveTodoSubscription from '../subscriptions/RemoveTodoSubscription';\nimport TodoListFooter from './TodoListFooter';\nimport TodoTextInput from './TodoTextInput';\n\nclass TodoApp extends React.Component {\n  static propTypes = {\n    viewer: React.PropTypes.object.isRequired,\n    relay: React.PropTypes.object.isRequired,\n    children: React.PropTypes.node.isRequired,\n  };\n\n  _handleTextInputSave = (text) => {\n    const { relay, viewer } = this.props;\n    relay.commitUpdate(\n      new AddTodoMutation({ viewer, text }),\n    );\n  };\n\n  render() {\n    const { viewer, children } = this.props;\n    const hasTodos = viewer.totalCount > 0;\n\n    return (\n      <div>\n        <section className=\"todoapp\">\n          <header className=\"header\">\n            <h1>\n              todos\n            </h1>\n            <TodoTextInput\n              autoFocus\n              className=\"new-todo\"\n              onSave={this._handleTextInputSave}\n              placeholder=\"What needs to be done?\"\n            />\n          </header>\n\n          {children}\n\n          {hasTodos && (\n            <TodoListFooter\n              todos={viewer.todos}\n              viewer={viewer}\n            />\n          )}\n        </section>\n        <footer className=\"info\">\n          <p>\n            Double-click to edit a todo\n          </p>\n          <p>\n            Created by the <a href=\"https://facebook.github.io/relay/\">\n              Relay team\n            </a>\n          </p>\n          <p>\n            Part of <a href=\"http://todomvc.com\">TodoMVC</a>\n          </p>\n        </footer>\n      </div>\n    );\n  }\n}\n\nexport default RelaySubscriptions.createContainer(TodoApp, {\n  fragments: {\n    viewer: () => Relay.QL`\n      fragment on User {\n        totalCount\n        ${AddTodoMutation.getFragment('viewer')}\n        ${TodoListFooter.getFragment('viewer')}\n        ${AddTodoSubscription.getFragment('viewer')}\n        ${RemoveTodoSubscription.getFragment('viewer')}\n      }\n    `,\n  },\n\n  subscriptions: [\n    ({ viewer }) => new AddTodoSubscription({ viewer }),\n    ({ viewer }) => new RemoveTodoSubscription({ viewer }),\n  ],\n});\n"
  },
  {
    "path": "examples/todo/js/components/TodoList.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport React from 'react';\nimport Relay from 'react-relay/classic';\n\nimport MarkAllTodosMutation from '../mutations/MarkAllTodosMutation';\nimport Todo from './Todo';\n\nclass TodoList extends React.Component {\n  static propTypes = {\n    viewer: React.PropTypes.object.isRequired,\n    relay: React.PropTypes.object.isRequired,\n  };\n\n  _handleMarkAllChange = (e) => {\n    const { relay, viewer } = this.props;\n    relay.commitUpdate(\n      new MarkAllTodosMutation({\n        todos: viewer.todos,\n        viewer,\n        complete: e.target.checked,\n      }),\n    );\n  };\n\n  render() {\n    const { viewer, relay } = this.props;\n    const numTodos = viewer.totalCount;\n    const numCompletedTodos = viewer.completedCount;\n\n    return (\n      <section className=\"main\">\n        <input\n          checked={numTodos === numCompletedTodos}\n          className=\"toggle-all\"\n          onChange={this._handleMarkAllChange}\n          type=\"checkbox\"\n        />\n        <label htmlFor=\"toggle-all\">\n          Mark all as complete\n        </label>\n        <ul className=\"todo-list\">\n          {viewer.todos.edges.map(edge => (\n            <Todo\n              key={edge.node.id}\n              todo={edge.node}\n              viewer={viewer}\n              pending={relay.hasOptimisticUpdate(edge)}\n            />\n          ))}\n        </ul>\n      </section>\n    );\n  }\n}\n\nexport default Relay.createContainer(TodoList, {\n  initialVariables: {\n    status: null,\n  },\n\n  prepareVariables({ status }) {\n    let nextStatus;\n    if (status === 'active' || status === 'completed') {\n      nextStatus = status;\n    } else {\n      // This matches the Backbone example, which displays all todos on an\n      // invalid route.\n      nextStatus = 'any';\n    }\n    return {\n      status: nextStatus,\n    };\n  },\n\n  fragments: {\n    viewer: () => Relay.QL`\n      fragment on User {\n        completedCount\n        todos(\n          status: $status\n          first: 2147483647  # max GraphQLInt\n        ) {\n          edges {\n            node {\n              id\n              ${Todo.getFragment('todo')}\n            }\n          }\n          ${MarkAllTodosMutation.getFragment('todos')}\n        }\n        totalCount\n        ${MarkAllTodosMutation.getFragment('viewer')}\n        ${Todo.getFragment('viewer')}\n      }\n    `,\n  },\n});\n"
  },
  {
    "path": "examples/todo/js/components/TodoListFooter.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport React from 'react';\nimport Relay from 'react-relay/classic';\nimport { IndexLink, Link } from 'react-router';\n\nimport RemoveCompletedTodosMutation\n  from '../mutations/RemoveCompletedTodosMutation';\n\nclass TodoListFooter extends React.Component {\n  static propTypes = {\n    viewer: React.PropTypes.object.isRequired,\n    relay: React.PropTypes.object.isRequired,\n  };\n\n  _handleRemoveCompletedTodosClick = () => {\n    const { relay, viewer } = this.props;\n    relay.commitUpdate(\n      new RemoveCompletedTodosMutation({\n        todos: viewer.todos,\n        viewer,\n      }),\n    );\n  };\n\n  render() {\n    const { viewer } = this.props;\n    const numCompletedTodos = viewer.completedCount;\n    const numRemainingTodos = viewer.totalCount - numCompletedTodos;\n\n    return (\n      <footer className=\"footer\">\n        <span className=\"todo-count\">\n          <strong>{numRemainingTodos}</strong> item{numRemainingTodos === 1 ? '' : 's'} left\n        </span>\n        <ul className=\"filters\">\n          <li>\n            <IndexLink to=\"/\" activeClassName=\"selected\">All</IndexLink>\n          </li>\n          <li>\n            <Link to=\"/active\" activeClassName=\"selected\">Active</Link>\n          </li>\n          <li>\n            <Link to=\"/completed\" activeClassName=\"selected\">Completed</Link>\n          </li>\n        </ul>\n        {numCompletedTodos > 0 && (\n          <button\n            className=\"clear-completed\"\n            onClick={this._handleRemoveCompletedTodosClick}\n          >\n            Clear completed\n          </button>\n        )}\n      </footer>\n    );\n  }\n}\n\nexport default Relay.createContainer(TodoListFooter, {\n  fragments: {\n    viewer: () => Relay.QL`\n      fragment on User {\n        completedCount\n        todos(\n          status: \"completed\"\n          first: 2147483647  # max GraphQLInt\n        ) {\n          ${RemoveCompletedTodosMutation.getFragment('todos')}\n        }\n        totalCount\n        ${RemoveCompletedTodosMutation.getFragment('viewer')}\n      }\n    `,\n  },\n});\n"
  },
  {
    "path": "examples/todo/js/components/TodoTextInput.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport React, { PropTypes } from 'react';\nimport ReactDOM from 'react-dom';\n\nconst ENTER_KEY_CODE = 13;\nconst ESC_KEY_CODE = 27;\n\nexport default class TodoTextInput extends React.Component {\n  static propTypes = {\n    className: PropTypes.string,\n    commitOnBlur: PropTypes.bool.isRequired,\n    initialValue: PropTypes.string,\n    onCancel: PropTypes.func,\n    onDelete: PropTypes.func,\n    onSave: PropTypes.func.isRequired,\n    placeholder: PropTypes.string,\n  };\n\n  static defaultProps = {\n    commitOnBlur: false,\n  };\n\n  state = {\n    isEditing: false,\n    text: this.props.initialValue || '',\n  };\n\n  componentDidMount() {\n    ReactDOM.findDOMNode(this).focus();\n  }\n\n  _commitChanges = () => {\n    const newText = this.state.text.trim();\n    if (this.props.onDelete && newText === '') {\n      this.props.onDelete();\n    } else if (this.props.onCancel && newText === this.props.initialValue) {\n      this.props.onCancel();\n    } else if (newText !== '') {\n      this.props.onSave(newText);\n      this.setState({ text: '' });\n    }\n  };\n\n  _handleBlur = () => {\n    if (this.props.commitOnBlur) {\n      this._commitChanges();\n    }\n  };\n\n  _handleChange = (e) => {\n    this.setState({ text: e.target.value });\n  };\n\n  _handleKeyDown = (e) => {\n    if (this.props.onCancel && e.keyCode === ESC_KEY_CODE) {\n      this.props.onCancel();\n    } else if (e.keyCode === ENTER_KEY_CODE) {\n      this._commitChanges();\n    }\n  };\n\n  render() {\n    return (\n      <input\n        className={this.props.className}\n        onBlur={this._handleBlur}\n        onChange={this._handleChange}\n        onKeyDown={this._handleKeyDown}\n        placeholder={this.props.placeholder}\n        value={this.state.text}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/AddTodoMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class AddTodoMutation extends Relay.Mutation {\n  static fragments = {\n    viewer: () => Relay.QL`\n      fragment on User {\n        id\n        totalCount\n      }\n    `,\n  };\n\n  getMutation() {\n    return Relay.QL`mutation { addTodo }`;\n  }\n\n  getFatQuery() {\n    return Relay.QL`\n      fragment on AddTodoPayload @relay(pattern: true) {\n        todoEdge\n        viewer {\n          todos\n          totalCount\n        }\n      }\n    `;\n  }\n\n  getConfigs() {\n    return [{\n      type: 'RANGE_ADD',\n      parentName: 'viewer',\n      parentID: this.props.viewer.id,\n      connectionName: 'todos',\n      edgeName: 'todoEdge',\n      rangeBehaviors: ({ status }) => (\n        status === 'completed' ? 'ignore' : 'append'\n      ),\n    }];\n  }\n\n  getVariables() {\n    return {\n      text: this.props.text,\n    };\n  }\n\n  getOptimisticResponse() {\n    return {\n      // FIXME: totalCount gets updated optimistically, but this edge does not\n      // get added until the server responds\n      todoEdge: {\n        node: {\n          complete: false,\n          text: this.props.text,\n        },\n      },\n      viewer: {\n        id: this.props.viewer.id,\n        totalCount: this.props.viewer.totalCount + 1,\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/ChangeTodoStatusMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class ChangeTodoStatusMutation extends Relay.Mutation {\n  static fragments = {\n    todo: () => Relay.QL`\n      fragment on Todo {\n        id,\n      }\n    `,\n    // TODO: Mark completedCount optional\n    viewer: () => Relay.QL`\n      fragment on User {\n        id,\n        completedCount,\n      }\n    `,\n  };\n  getMutation() {\n    return Relay.QL`mutation{changeTodoStatus}`;\n  }\n  getFatQuery() {\n    return Relay.QL`\n      fragment on ChangeTodoStatusPayload @relay(pattern: true) {\n        todo {\n          complete,\n        },\n        viewer {\n          completedCount,\n          todos,\n        },\n      }\n    `;\n  }\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        todo: this.props.todo.id,\n        viewer: this.props.viewer.id,\n      },\n    }];\n  }\n  getVariables() {\n    return {\n      complete: this.props.complete,\n      id: this.props.todo.id,\n    };\n  }\n  getOptimisticResponse() {\n    const viewerPayload = { id: this.props.viewer.id };\n    if (this.props.viewer.completedCount != null) {\n      viewerPayload.completedCount = this.props.complete ?\n        this.props.viewer.completedCount + 1 :\n        this.props.viewer.completedCount - 1;\n    }\n    return {\n      todo: {\n        complete: this.props.complete,\n        id: this.props.todo.id,\n      },\n      viewer: viewerPayload,\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/MarkAllTodosMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class MarkAllTodosMutation extends Relay.Mutation {\n  static fragments = {\n    // TODO: Mark edges and totalCount optional\n    todos: () => Relay.QL`\n      fragment on TodoConnection {\n        edges {\n          node {\n            complete,\n            id,\n          },\n        },\n      }\n    `,\n    viewer: () => Relay.QL`\n      fragment on User {\n        id,\n        totalCount,\n      }\n    `,\n  };\n  getMutation() {\n    return Relay.QL`mutation{markAllTodos}`;\n  }\n  getFatQuery() {\n    return Relay.QL`\n      fragment on MarkAllTodosPayload @relay(pattern: true) {\n        viewer {\n          completedCount,\n          todos,\n        },\n      }\n    `;\n  }\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        viewer: this.props.viewer.id,\n      },\n    }];\n  }\n  getVariables() {\n    return {\n      complete: this.props.complete,\n    };\n  }\n  getOptimisticResponse() {\n    const viewerPayload = { id: this.props.viewer.id };\n    if (this.props.todos && this.props.todos.edges) {\n      viewerPayload.todos = {\n        edges: this.props.todos.edges\n          .filter(edge => edge.node.complete !== this.props.complete)\n          .map(edge => ({\n            node: {\n              complete: this.props.complete,\n              id: edge.node.id,\n            },\n          })),\n      };\n    }\n    if (this.props.viewer.totalCount != null) {\n      viewerPayload.completedCount = this.props.complete ?\n        this.props.viewer.totalCount :\n        0;\n    }\n    return {\n      viewer: viewerPayload,\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/RemoveCompletedTodosMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class RemoveCompletedTodosMutation extends Relay.Mutation {\n  static fragments = {\n    // TODO: Make completedCount, edges, and totalCount optional\n    todos: () => Relay.QL`\n      fragment on TodoConnection {\n        edges {\n          node {\n            complete,\n            id,\n          },\n        },\n      }\n    `,\n    viewer: () => Relay.QL`\n      fragment on User {\n        completedCount,\n        id,\n        totalCount,\n      }\n    `,\n  };\n  getMutation() {\n    return Relay.QL`mutation{removeCompletedTodos}`;\n  }\n  getFatQuery() {\n    return Relay.QL`\n      fragment on RemoveCompletedTodosPayload @relay(pattern: true) {\n        deletedTodoIds,\n        viewer {\n          completedCount,\n          totalCount,\n        },\n      }\n    `;\n  }\n  getConfigs() {\n    return [{\n      type: 'NODE_DELETE',\n      parentName: 'viewer',\n      parentID: this.props.viewer.id,\n      connectionName: 'todos',\n      deletedIDFieldName: 'deletedTodoIds',\n    }];\n  }\n  getVariables() {\n    return {};\n  }\n  getOptimisticResponse() {\n    let deletedTodoIds;\n    let newTotalCount;\n    if (this.props.todos && this.props.todos.edges) {\n      deletedTodoIds = this.props.todos.edges\n        .filter(edge => edge.node.complete)\n        .map(edge => edge.node.id);\n    }\n    const { completedCount, totalCount } = this.props.viewer;\n    if (completedCount != null && totalCount != null) {\n      newTotalCount = totalCount - completedCount;\n    }\n    return {\n      deletedTodoIds,\n      viewer: {\n        completedCount: 0,\n        id: this.props.viewer.id,\n        totalCount: newTotalCount,\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/RemoveTodoMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class RemoveTodoMutation extends Relay.Mutation {\n  static fragments = {\n    // TODO: Mark complete as optional\n    todo: () => Relay.QL`\n      fragment on Todo {\n        complete,\n        id,\n      }\n    `,\n    // TODO: Mark completedCount and totalCount as optional\n    viewer: () => Relay.QL`\n      fragment on User {\n        completedCount,\n        id,\n        totalCount,\n      }\n    `,\n  };\n  getMutation() {\n    return Relay.QL`mutation{removeTodo}`;\n  }\n  getFatQuery() {\n    return Relay.QL`\n      fragment on RemoveTodoPayload @relay(pattern: true) {\n        deletedTodoId,\n        viewer {\n          completedCount,\n          totalCount,\n        },\n      }\n    `;\n  }\n  getConfigs() {\n    return [{\n      type: 'NODE_DELETE',\n      parentName: 'viewer',\n      parentID: this.props.viewer.id,\n      connectionName: 'todos',\n      deletedIDFieldName: 'deletedTodoId',\n    }];\n  }\n  getVariables() {\n    return {\n      id: this.props.todo.id,\n    };\n  }\n  getOptimisticResponse() {\n    const viewerPayload = { id: this.props.viewer.id };\n    if (this.props.viewer.completedCount != null) {\n      viewerPayload.completedCount = this.props.todo.complete === true ?\n        this.props.viewer.completedCount - 1 :\n        this.props.viewer.completedCount;\n    }\n    if (this.props.viewer.totalCount != null) {\n      viewerPayload.totalCount = this.props.viewer.totalCount - 1;\n    }\n    return {\n      deletedTodoId: this.props.todo.id,\n      viewer: viewerPayload,\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/mutations/RenameTodoMutation.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default class RenameTodoMutation extends Relay.Mutation {\n  static fragments = {\n    todo: () => Relay.QL`\n      fragment on Todo {\n        id,\n      }\n    `,\n  };\n  getMutation() {\n    return Relay.QL`mutation{renameTodo}`;\n  }\n  getFatQuery() {\n    return Relay.QL`\n      fragment on RenameTodoPayload @relay(pattern: true) {\n        todo {\n          text,\n        }\n      }\n    `;\n  }\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        todo: this.props.todo.id,\n      },\n    }];\n  }\n  getVariables() {\n    return {\n      id: this.props.todo.id,\n      text: this.props.text,\n    };\n  }\n  getOptimisticResponse() {\n    return {\n      todo: {\n        id: this.props.todo.id,\n        text: this.props.text,\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/queries/ViewerQueries.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport Relay from 'react-relay/classic';\n\nexport default {\n  viewer: () => Relay.QL`query { viewer }`,\n};\n"
  },
  {
    "path": "examples/todo/js/subscriptions/AddTodoSubscription.js",
    "content": "import Relay from 'react-relay/classic';\nimport { Subscription } from 'relay-subscriptions';\n\nimport Todo from '../components/Todo';\n\nexport default class AddTodoSubscription extends Subscription {\n  static fragments = {\n    viewer: () => Relay.QL`\n      fragment on User {\n        id\n      }\n    `,\n  };\n\n  getSubscription() {\n    return Relay.QL`\n      subscription {\n        addTodoSubscription(input: $input) {\n          todoEdge {\n            __typename\n            node {\n              ${Todo.getFragment('todo')}\n            }\n          }\n          viewer {\n            id\n            totalCount\n          }\n        }\n      }\n    `;\n  }\n\n  getConfigs() {\n    return [{\n      type: 'RANGE_ADD',\n      parentName: 'viewer',\n      parentID: this.props.viewer.id,\n      connectionName: 'todos',\n      edgeName: 'todoEdge',\n      rangeBehaviors: () => 'append',\n    }];\n  }\n\n  getVariables() {\n    return {};\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/subscriptions/RemoveTodoSubscription.js",
    "content": "import Relay from 'react-relay/classic';\nimport { Subscription } from 'relay-subscriptions';\n\nexport default class RemoveTodoSubscription extends Subscription {\n  static fragments = {\n    viewer: () => Relay.QL`\n      fragment on User {\n        id\n      }\n    `,\n  };\n\n  getSubscription() {\n    return Relay.QL`\n      subscription {\n        removeTodoSubscription(input: $input) {\n          deletedTodoId\n          viewer {\n            completedCount\n            totalCount\n          }\n        }\n      }\n    `;\n  }\n\n  getConfigs() {\n    return [{\n      type: 'NODE_DELETE',\n      parentName: 'viewer',\n      parentID: this.props.viewer.id,\n      connectionName: 'todos',\n      deletedIDFieldName: 'deletedTodoId',\n    }];\n  }\n\n  getVariables() {\n    return {};\n  }\n}\n"
  },
  {
    "path": "examples/todo/js/subscriptions/UpdateTodoSubscription.js",
    "content": "import Relay from 'react-relay/classic';\nimport { Subscription } from 'relay-subscriptions';\n\nimport Todo from '../components/Todo';\n\nexport default class UpdateTodoSubscription extends Subscription {\n  static fragments = {\n    todo: () => Relay.QL`\n      fragment on Todo {\n        id\n      }\n    `,\n  };\n\n  getSubscription() {\n    return Relay.QL`\n      subscription {\n        updateTodoSubscription(input: $input) {\n          todo {\n            ${Todo.getFragment('todo')}\n          }\n          viewer {\n            id\n            completedCount\n          }\n        }\n      }\n    `;\n  }\n\n  getConfigs() {\n    return [{\n      type: 'FIELDS_CHANGE',\n      fieldIDs: {\n        todo: this.props.todo.id,\n      },\n    }];\n  }\n\n  getVariables() {\n    return {\n      id: this.props.todo.id,\n    };\n  }\n}\n"
  },
  {
    "path": "examples/todo/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"babel-node server.js\",\n    \"update-schema\": \"babel-node tools/updateSchema.js\"\n  },\n  \"dependencies\": {\n    \"babel-cli\": \"^6.26.0\",\n    \"babel-core\": \"^6.26.0\",\n    \"babel-loader\": \"^7.1.2\",\n    \"babel-plugin-relay\": \"^1.4.1\",\n    \"babel-polyfill\": \"^6.26.0\",\n    \"babel-preset-env\": \"^1.6.1\",\n    \"babel-preset-react\": \"^6.24.1\",\n    \"babel-preset-stage-2\": \"^6.24.1\",\n    \"classnames\": \"^2.2.5\",\n    \"express\": \"^4.16.2\",\n    \"express-graphql\": \"^0.6.11\",\n    \"graphql\": \"^0.11.7\",\n    \"graphql-relay\": \"^0.5.3\",\n    \"graphql-relay-subscription\": \"^0.2.0\",\n    \"history\": \"^2.1.2\",\n    \"react\": \"^15.5.4\",\n    \"react-dom\": \"^15.5.4\",\n    \"react-relay\": \"^1.4.1\",\n    \"react-router\": \"^2.8.1\",\n    \"react-router-relay\": \"^0.14.0\",\n    \"relay-subscriptions\": \"^2.0.2\",\n    \"subscriptions-transport-ws\": \"^0.9.4\",\n    \"todomvc-app-css\": \"^2.1.0\",\n    \"todomvc-common\": \"^1.0.4\",\n    \"webpack\": \"^3.8.1\",\n    \"webpack-dev-server\": \"^2.9.4\"\n  }\n}\n"
  },
  {
    "path": "examples/todo/public/base.css",
    "content": "hr {\n\tmargin: 20px 0;\n\tborder: 0;\n\tborder-top: 1px dashed #c5c5c5;\n\tborder-bottom: 1px dashed #f7f7f7;\n}\n\n.learn a {\n\tfont-weight: normal;\n\ttext-decoration: none;\n\tcolor: #b83f45;\n}\n\n.learn a:hover {\n\ttext-decoration: underline;\n\tcolor: #787e7e;\n}\n\n.learn h3,\n.learn h4,\n.learn h5 {\n\tmargin: 10px 0;\n\tfont-weight: 500;\n\tline-height: 1.2;\n\tcolor: #000;\n}\n\n.learn h3 {\n\tfont-size: 24px;\n}\n\n.learn h4 {\n\tfont-size: 18px;\n}\n\n.learn h5 {\n\tmargin-bottom: 0;\n\tfont-size: 14px;\n}\n\n.learn ul {\n\tpadding: 0;\n\tmargin: 0 0 30px 25px;\n}\n\n.learn li {\n\tline-height: 20px;\n}\n\n.learn p {\n\tfont-size: 15px;\n\tfont-weight: 300;\n\tline-height: 1.3;\n\tmargin-top: 0;\n\tmargin-bottom: 0;\n}\n\n#issue-count {\n\tdisplay: none;\n}\n\n.quote {\n\tborder: none;\n\tmargin: 20px 0 60px 0;\n}\n\n.quote p {\n\tfont-style: italic;\n}\n\n.quote p:before {\n\tcontent: '“';\n\tfont-size: 50px;\n\topacity: .15;\n\tposition: absolute;\n\ttop: -20px;\n\tleft: 3px;\n}\n\n.quote p:after {\n\tcontent: '”';\n\tfont-size: 50px;\n\topacity: .15;\n\tposition: absolute;\n\tbottom: -42px;\n\tright: 3px;\n}\n\n.quote footer {\n\tposition: absolute;\n\tbottom: -40px;\n\tright: 0;\n}\n\n.quote footer img {\n\tborder-radius: 3px;\n}\n\n.quote footer a {\n\tmargin-left: 5px;\n\tvertical-align: middle;\n}\n\n.speech-bubble {\n\tposition: relative;\n\tpadding: 10px;\n\tbackground: rgba(0, 0, 0, .04);\n\tborder-radius: 5px;\n}\n\n.speech-bubble:after {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 100%;\n\tright: 30px;\n\tborder: 13px solid transparent;\n\tborder-top-color: rgba(0, 0, 0, .04);\n}\n\n.learn-bar > .learn {\n\tposition: absolute;\n\twidth: 272px;\n\ttop: 8px;\n\tleft: -300px;\n\tpadding: 10px;\n\tborder-radius: 5px;\n\tbackground-color: rgba(255, 255, 255, .6);\n\ttransition-property: left;\n\ttransition-duration: 500ms;\n}\n\n@media (min-width: 899px) {\n\t.learn-bar {\n\t\twidth: auto;\n\t\tpadding-left: 300px;\n\t}\n\n\t.learn-bar > .learn {\n\t\tleft: 8px;\n\t}\n}\n"
  },
  {
    "path": "examples/todo/public/index.css",
    "content": "html,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbutton {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tfont-size: 100%;\n\tvertical-align: baseline;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tcolor: inherit;\n\t-webkit-appearance: none;\n\tappearance: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n\tfont: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n\tline-height: 1.4em;\n\tbackground: #f5f5f5;\n\tcolor: #4d4d4d;\n\tmin-width: 230px;\n\tmax-width: 550px;\n\tmargin: 0 auto;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tfont-weight: 300;\n}\n\n:focus {\n\toutline: 0;\n}\n\n.hidden {\n\tdisplay: none;\n}\n\n.todoapp {\n\tbackground: #fff;\n\tmargin: 130px 0 40px 0;\n\tposition: relative;\n\tbox-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\n\t            0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp input::-webkit-input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp input::-moz-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp input::input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp h1 {\n\tposition: absolute;\n\ttop: -155px;\n\twidth: 100%;\n\tfont-size: 100px;\n\tfont-weight: 100;\n\ttext-align: center;\n\tcolor: rgba(175, 47, 47, 0.15);\n\t-webkit-text-rendering: optimizeLegibility;\n\t-moz-text-rendering: optimizeLegibility;\n\ttext-rendering: optimizeLegibility;\n}\n\n.new-todo,\n.edit {\n\tposition: relative;\n\tmargin: 0;\n\twidth: 100%;\n\tfont-size: 24px;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tline-height: 1.4em;\n\tborder: 0;\n\tcolor: inherit;\n\tpadding: 6px;\n\tborder: 1px solid #999;\n\tbox-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n\tbox-sizing: border-box;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.new-todo {\n\tpadding: 16px 16px 16px 60px;\n\tborder: none;\n\tbackground: rgba(0, 0, 0, 0.003);\n\tbox-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\n}\n\n.main {\n\tposition: relative;\n\tz-index: 2;\n\tborder-top: 1px solid #e6e6e6;\n}\n\nlabel[for='toggle-all'] {\n\tdisplay: none;\n}\n\n.toggle-all {\n\tposition: absolute;\n\ttop: -55px;\n\tleft: -12px;\n\twidth: 60px;\n\theight: 34px;\n\ttext-align: center;\n\tborder: none; /* Mobile Safari */\n}\n\n.toggle-all:before {\n\tcontent: '❯';\n\tfont-size: 22px;\n\tcolor: #e6e6e6;\n\tpadding: 10px 27px 10px 27px;\n}\n\n.toggle-all:checked:before {\n\tcolor: #737373;\n}\n\n.todo-list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.todo-list li {\n\tposition: relative;\n\tfont-size: 24px;\n\tborder-bottom: 1px solid #ededed;\n}\n\n.todo-list li:last-child {\n\tborder-bottom: none;\n}\n\n.todo-list li.editing {\n\tborder-bottom: none;\n\tpadding: 0;\n}\n\n.todo-list li.editing .edit {\n\tdisplay: block;\n\twidth: 506px;\n\tpadding: 12px 16px;\n\tmargin: 0 0 0 43px;\n}\n\n.todo-list li.editing .view {\n\tdisplay: none;\n}\n\n.todo-list li .toggle {\n\ttext-align: center;\n\twidth: 40px;\n\t/* auto, since non-WebKit browsers doesn't support input styling */\n\theight: auto;\n\tposition: absolute;\n\ttop: 0;\n\tbottom: 0;\n\tmargin: auto 0;\n\tborder: none; /* Mobile Safari */\n\t-webkit-appearance: none;\n\tappearance: none;\n}\n\n.todo-list li .toggle:after {\n\tcontent: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#ededed\" stroke-width=\"3\"/></svg>');\n}\n\n.todo-list li .toggle:checked:after {\n\tcontent: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#bddad5\" stroke-width=\"3\"/><path fill=\"#5dc2af\" d=\"M72 25L42 71 27 56l-4 4 20 20 34-52z\"/></svg>');\n}\n\n.todo-list li label {\n\tword-break: break-all;\n\tpadding: 15px 60px 15px 15px;\n\tmargin-left: 45px;\n\tdisplay: block;\n\tline-height: 1.2;\n\ttransition: color 0.4s;\n}\n\n.todo-list li.completed label {\n\tcolor: #d9d9d9;\n\ttext-decoration: line-through;\n}\n\n.todo-list li .destroy {\n\tdisplay: none;\n\tposition: absolute;\n\ttop: 0;\n\tright: 10px;\n\tbottom: 0;\n\twidth: 40px;\n\theight: 40px;\n\tmargin: auto 0;\n\tfont-size: 30px;\n\tcolor: #cc9a9a;\n\tmargin-bottom: 11px;\n\ttransition: color 0.2s ease-out;\n}\n\n.todo-list li .destroy:hover {\n\tcolor: #af5b5e;\n}\n\n.todo-list li .destroy:after {\n\tcontent: '×';\n}\n\n.todo-list li:hover .destroy {\n\tdisplay: block;\n}\n\n.todo-list li .edit {\n\tdisplay: none;\n}\n\n.todo-list li.editing:last-child {\n\tmargin-bottom: -1px;\n}\n\n.footer {\n\tcolor: #777;\n\tpadding: 10px 15px;\n\theight: 20px;\n\ttext-align: center;\n\tborder-top: 1px solid #e6e6e6;\n}\n\n.footer:before {\n\tcontent: '';\n\tposition: absolute;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\theight: 50px;\n\toverflow: hidden;\n\tbox-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\n\t            0 8px 0 -3px #f6f6f6,\n\t            0 9px 1px -3px rgba(0, 0, 0, 0.2),\n\t            0 16px 0 -6px #f6f6f6,\n\t            0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todo-count {\n\tfloat: left;\n\ttext-align: left;\n}\n\n.todo-count strong {\n\tfont-weight: 300;\n}\n\n.filters {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n\tposition: absolute;\n\tright: 0;\n\tleft: 0;\n}\n\n.filters li {\n\tdisplay: inline;\n}\n\n.filters li a {\n\tcolor: inherit;\n\tmargin: 3px;\n\tpadding: 3px 7px;\n\ttext-decoration: none;\n\tborder: 1px solid transparent;\n\tborder-radius: 3px;\n}\n\n.filters li a:hover {\n\tborder-color: rgba(175, 47, 47, 0.1);\n}\n\n.filters li a.selected {\n\tborder-color: rgba(175, 47, 47, 0.2);\n}\n\n.clear-completed,\nhtml .clear-completed:active {\n\tfloat: right;\n\tposition: relative;\n\tline-height: 20px;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.clear-completed:hover {\n\ttext-decoration: underline;\n}\n\n.info {\n\tmargin: 65px auto 0;\n\tcolor: #bfbfbf;\n\tfont-size: 10px;\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\ttext-align: center;\n}\n\n.info p {\n\tline-height: 1;\n}\n\n.info a {\n\tcolor: inherit;\n\ttext-decoration: none;\n\tfont-weight: 400;\n}\n\n.info a:hover {\n\ttext-decoration: underline;\n}\n\n/*\n\tHack to remove background from Mobile Safari.\n\tCan't use it globally since it destroys checkboxes in Firefox\n*/\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n\t.toggle-all,\n\t.todo-list li .toggle {\n\t\tbackground: none;\n\t}\n\n\t.todo-list li .toggle {\n\t\theight: 40px;\n\t}\n\n\t.toggle-all {\n\t\t-webkit-transform: rotate(90deg);\n\t\ttransform: rotate(90deg);\n\t\t-webkit-appearance: none;\n\t\tappearance: none;\n\t}\n}\n\n@media (max-width: 430px) {\n\t.footer {\n\t\theight: 50px;\n\t}\n\n\t.filters {\n\t\tbottom: 10px;\n\t}\n}\n"
  },
  {
    "path": "examples/todo/public/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" data-framework=\"relay\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Relay • TodoMVC</title>\n    <link rel=\"stylesheet\" href=\"base.css\">\n    <link rel=\"stylesheet\" href=\"index.css\">\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"text/javascript\">\n      // Force `fetch` polyfill to workaround Chrome not displaying request body\n      // in developer tools for the native `fetch`.\n      self.fetch = null;\n    </script>\n    <script src=\"http://localhost:3000/webpack-dev-server.js\"></script>\n    <script src=\"js/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/todo/public/learn.json",
    "content": "{\n  \"relay\": {\n    \"name\": \"Relay\",\n    \"description\": \"A JavaScript framework for building data-driven React applications\",\n    \"homepage\": \"facebook.github.io/relay/\",\n    \"examples\": [{\n      \"name\": \"Relay + express-graphql Example\",\n      \"url\": \"\",\n      \"source_url\": \"https://github.com/facebook/relay/tree/master/examples/todo\",\n      \"type\": \"backend\"\n    }],\n    \"link_groups\": [{\n      \"heading\": \"Official Resources\",\n      \"links\": [{\n        \"name\": \"Documentation\",\n        \"url\": \"https://facebook.github.io/relay/docs/getting-started.html\"\n      }, {\n        \"name\": \"API Reference\",\n        \"url\": \"https://facebook.github.io/relay/docs/api-reference-relay.html\"\n      }, {\n        \"name\": \"Relay on GitHub\",\n        \"url\": \"https://github.com/facebook/relay\"\n      }]\n    }, {\n      \"heading\": \"Community\",\n      \"links\": [{\n        \"name\": \"Relay on StackOverflow\",\n        \"url\": \"https://stackoverflow.com/questions/tagged/relayjs\"\n      }]\n    }]\n  },\n  \"templates\": {\n    \"todomvc\": \"<header> <h3><%= name %></h3> <span class=\\\"source-links\\\"> <% if (typeof examples !== 'undefined') { %> <% examples.forEach(function (example) { %> <h5><%= example.name %></h5> <% if (!location.href.match(example.url + '/')) { %> <a class=\\\"demo-link\\\" data-type=\\\"<%= example.type === 'backend' ? 'external' : 'local' %>\\\" href=\\\"<%= example.url %>\\\">Demo</a>, <% } if (example.type === 'backend') { %><a href=\\\"<%= example.source_url %>\\\"><% } else { %><a href=\\\"https://github.com/tastejs/todomvc/tree/gh-pages/<%= example.source_url ? example.source_url : example.url %>\\\"><% } %>Source</a> <% }); %> <% } %> </span> </header> <hr> <blockquote class=\\\"quote speech-bubble\\\"> <p><%= description %></p> <footer> <a href=\\\"http://<%= homepage %>\\\"><%= name %></a> </footer> </blockquote> <% if (typeof link_groups !== 'undefined') { %> <hr> <% link_groups.forEach(function (link_group) { %> <h4><%= link_group.heading %></h4> <ul> <% link_group.links.forEach(function (link) { %> <li> <a href=\\\"<%= link.url %>\\\"><%= link.name %></a> </li> <% }); %> </ul> <% }); %> <% } %> <footer> <hr> <em>If you have other helpful links to share, or find any of the links above no longer work, please <a href=\\\"https://github.com/tastejs/todomvc/issues\\\">let us know</a>.</em> </footer>\"\n  }\n}\n"
  },
  {
    "path": "examples/todo/server.js",
    "content": "/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/* eslint-disable no-console */\n\nimport express from 'express';\nimport graphQLHTTP from 'express-graphql';\nimport { execute, subscribe } from 'graphql';\nimport path from 'path';\nimport { SubscriptionServer } from 'subscriptions-transport-ws';\nimport webpack from 'webpack';\nimport WebpackDevServer from 'webpack-dev-server';\n\nimport { addNotifier } from './data/database';\nimport { schema } from './data/schema';\n\nconst APP_PORT = 3000;\nconst GRAPHQL_PORT = 8080;\n\n// Expose a GraphQL endpoint\nconst graphQLApp = express();\ngraphQLApp.use('/', graphQLHTTP({ schema, pretty: true, graphiql: true }));\n\nconst graphQLServer = graphQLApp.listen(GRAPHQL_PORT, () => {\n  console.log(\n    `GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}`\n  );\n});\n\nclass AsyncQueue {\n  constructor(unsubscribe) {\n    this.unsubscribe = unsubscribe;\n\n    this.values = [];\n    this.createPromise();\n\n    this.iterable = this.createIterable();\n  }\n\n  createPromise() {\n    this.promise = new Promise((resolve) => {\n      this.resolvePromise = resolve;\n    });\n  }\n\n  async * createIterable() {\n    try {\n      while (true) { // eslint-disable-line no-constant-condition\n        await this.promise; // eslint-disable-line no-unused-expressions\n\n        for (const value of this.values) {\n          yield value;\n        }\n\n        this.values.length = 0;\n        this.createPromise();\n      }\n    } finally {\n      this.unsubscribe();\n    }\n  }\n\n  push(value) {\n    this.values.push(value);\n    this.resolvePromise();\n  }\n}\n\nSubscriptionServer.create(\n  {\n    schema,\n    execute,\n    subscribe,\n\n    onConnect: (payload, socket) => {\n      const topics = Object.create(null);\n\n      // eslint-disable-next-line no-param-reassign\n      socket.removeNotifier = addNotifier(({ topic, data }) => {\n        const topicQueues = topics[topic];\n        if (!topicQueues) {\n          return;\n        }\n\n        topicQueues.forEach((queue) => {\n          queue.push(data);\n        });\n      });\n\n      function unsubscribe(topic, queue) {\n        const topicQueues = topics[topic];\n\n        const index = topicQueues.indexOf(queue);\n        if (index === -1) {\n          return;\n        }\n\n        topicQueues.splice(index, 1);\n        console.log('removed subscription for %s', topic);\n      }\n\n      return {\n        subscribe: (topic) => {\n          if (!topics[topic]) {\n            topics[topic] = [];\n          }\n\n          const queue = new AsyncQueue(() => {\n            unsubscribe(topic, queue);\n          });\n\n          topics[topic].push(queue);\n          console.log('added subscription for %s', topic);\n\n          return queue.iterable;\n        },\n      };\n    },\n\n    onDisconnect: (socket) => {\n      console.log('socket disconnect');\n      socket.removeNotifier();\n    },\n  },\n  {\n    server: graphQLServer,\n  },\n);\n\n// Serve the Relay app.\nconst compiler = webpack({\n  entry: [\n    'babel-polyfill',\n    './js/app.js',\n  ],\n\n  output: {\n    path: '/',\n    filename: 'app.js',\n  },\n\n  module: {\n    rules: [\n      { test: /\\.js$/, exclude: /node_modules/, use: 'babel-loader' },\n    ],\n  },\n\n  devtool: 'sourcemap',\n});\n\nconst app = new WebpackDevServer(compiler, {\n  contentBase: '/public/',\n  proxy: {\n    '/graphql': {\n      target: `http://localhost:${GRAPHQL_PORT}`,\n      ws: true,\n    },\n  },\n  publicPath: '/js/',\n  stats: { colors: true },\n});\n\n// Serve static resources\napp.use('/', express.static(path.join(__dirname, 'public')));\napp.listen(APP_PORT, () => {\n  console.log(`App is now running on http://localhost:${APP_PORT}`);\n});\n"
  },
  {
    "path": "examples/todo/tools/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"no-console\": 0\n  }\n}\n"
  },
  {
    "path": "examples/todo/tools/updateSchema.js",
    "content": "#!/usr/bin/env babel-node\n/**\n * This file provided by Facebook is for non-commercial testing and evaluation\n * purposes only.  Facebook reserves all rights not expressly granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { schema } from '../data/schema';\nimport { printSchema } from 'graphql/utilities';\n\nfs.writeFileSync(\n  path.join(__dirname, '../data/schema.graphql'),\n  printSchema(schema)\n);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"relay-subscriptions\",\n  \"version\": \"2.0.2\",\n  \"description\": \"Subscription support for Relay\",\n  \"main\": \"lib/index.js\",\n  \"scripts\": {\n    \"build\": \"rimraf lib && babel src --ignore __tests__ --out-dir lib\",\n    \"check\": \"flow check src\",\n    \"lint\": \"eslint examples src\",\n    \"prepublish\": \"npm run build\",\n    \"tdd\": \"jest --watch\",\n    \"test\": \"npm run lint && npm run testonly\",\n    \"testonly\": \"jest --runInBand --verbose\",\n    \"watch\": \"npm run build -- --watch\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/edvinerikson/relay-subscriptions.git\"\n  },\n  \"files\": [\n    \"lib\"\n  ],\n  \"keywords\": [\n    \"relay\",\n    \"react\",\n    \"subscriptions\"\n  ],\n  \"author\": \"Edvin Eriksson <edvinerikson@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/edvinerikson/relay-subscriptions/issues\"\n  },\n  \"homepage\": \"https://github.com/edvinerikson/relay-subscriptions#readme\",\n  \"dependencies\": {\n    \"invariant\": \"^2.2.2\",\n    \"lodash\": \"^4.17.4\",\n    \"prop-types\": \"^15.5.10\",\n    \"warning\": \"^3.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^0.14.9 || >=15.3.0\",\n    \"react-relay\": \">=1.0.0\"\n  },\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.24.1\",\n    \"babel-eslint\": \"^7.2.3\",\n    \"babel-jest\": \"^20.0.3\",\n    \"babel-plugin-add-module-exports\": \"^0.2.1\",\n    \"babel-plugin-dev-expression\": \"^0.2.1\",\n    \"babel-preset-env\": \"^1.5.1\",\n    \"babel-preset-react\": \"^6.24.1\",\n    \"babel-preset-stage-2\": \"^6.24.1\",\n    \"enzyme\": \"^2.8.2\",\n    \"eslint\": \"^2.13.1\",\n    \"eslint-config-airbnb\": \"^9.0.1\",\n    \"eslint-plugin-flowtype\": \"^2.34.0\",\n    \"eslint-plugin-import\": \"^1.14.0\",\n    \"eslint-plugin-jsx-a11y\": \"^1.5.5\",\n    \"eslint-plugin-react\": \"^5.2.2\",\n    \"flow-bin\": \"^0.47.0\",\n    \"jest\": \"^20.0.4\",\n    \"react\": \"^15.5.4\",\n    \"react-dom\": \"^15.5.4\",\n    \"react-relay\": \"^1.0.0\",\n    \"react-test-renderer\": \"^15.5.4\",\n    \"rimraf\": \"^2.6.1\"\n  }\n}\n"
  },
  {
    "path": "src/.flowconfig",
    "content": "[ignore]\n\n[include]\n../node_modules/invariant\n../node_modules/lodash\n../node_modules/react\n../node_modules/react-relay\n../node_modules/warning\n\n[libs]\n\n[options]\nesproposal.class_static_fields=enable\n"
  },
  {
    "path": "src/Environment.js",
    "content": "/* @flow */\n\nimport invariant from 'invariant';\nimport Relay from 'react-relay/classic';\nimport RelayNetworkLayer from 'react-relay/lib/RelayNetworkLayer';\nimport RelayStoreData from 'react-relay/lib/RelayStoreData';\n\nimport createSubscriptionQuery from './createSubscriptionQuery';\nimport type Subscription from './Subscription';\nimport SubscriptionRequest from './SubscriptionRequest';\nimport type { SubscriptionDisposable, SubscriptionObserver } from './types';\nimport updateStoreData from './updateStoreData';\n\n// Override a few Relay classes to use our own network layer proxy that\n// supports sendSubscription.\n\nclass NetworkLayer extends RelayNetworkLayer {\n  sendSubscription(\n    subscriptionRequest: SubscriptionRequest,\n  ): SubscriptionDisposable {\n    const implementation = this._getImplementation();\n    invariant(\n      implementation.sendSubscription,\n      'NetworkLayer: Network layer implementation does not support ' +\n      'subscriptions.',\n    );\n\n    return implementation.sendSubscription(subscriptionRequest);\n  }\n}\n\nclass StoreData extends RelayStoreData {\n  _networkLayer: NetworkLayer;\n\n  constructor(...args) {\n    super(...args);\n\n    this._networkLayer = new NetworkLayer();\n  }\n}\n\nexport default class Environment extends Relay.Environment {\n  startSubscription: (\n    subscription: Subscription<any>,\n    observer?: SubscriptionObserver,\n  ) => SubscriptionDisposable;\n\n  constructor(storeData?: StoreData) {\n    super(storeData || new StoreData());\n\n    this.startSubscription = this.startSubscription.bind(this);\n\n    this._nextClientSubscriptionId = 0;\n  }\n\n  startSubscription(\n    subscription: Subscription<any>,\n    observer?: SubscriptionObserver,\n  ): SubscriptionDisposable {\n    const clientSubscriptionId = this._nextClientSubscriptionId.toString(36);\n    ++this._nextClientSubscriptionId;\n\n    subscription.bindEnvironment(this);\n\n    const query = createSubscriptionQuery(subscription.getSubscription(), {\n      input: {\n        ...subscription.getVariables(),\n        clientSubscriptionId,\n      },\n    });\n\n    const onPayload = (payload) => {\n      updateStoreData(this, subscription.getConfigs(), query, payload);\n    };\n\n    let requestObserver;\n    if (observer) {\n      const definedObserver = observer; // Placate Flow.\n      requestObserver = {\n        onNext: (payload) => {\n          onPayload(payload);\n          if (definedObserver.onNext) {\n            definedObserver.onNext(payload);\n          }\n        },\n        onError: (error) => {\n          if (definedObserver.onError) {\n            definedObserver.onError(error);\n          }\n        },\n        onCompleted: (value) => {\n          if (definedObserver.onCompleted) {\n            definedObserver.onCompleted(value);\n          }\n        },\n      };\n    } else {\n      requestObserver = {\n        onNext: onPayload,\n        onError: () => {},\n        onCompleted: () => {},\n      };\n    }\n\n    return this._storeData.getNetworkLayer().sendSubscription(\n      new SubscriptionRequest(query, requestObserver),\n    );\n  }\n}\n"
  },
  {
    "path": "src/Subscription.js",
    "content": "/* @flow */\n\nimport invariant from 'invariant';\nimport Relay from 'react-relay/classic';\nimport RelayMetaRoute from 'react-relay/lib/RelayMetaRoute';\n\nimport type {\n  MutationConfig,\n  RelayConcreteNode,\n  Variables,\n} from './types';\n\nexport default class Subscription<Tp: Object> {\n  static name: string;\n  static initialVariables: Variables;\n  static prepareVariables: ?(\n    prevVariables: Variables,\n    route: RelayMetaRoute\n  ) => Variables;\n\n  props: Tp;\n  _unresolvedProps: Tp;\n  _environment: Relay.Environment;\n  _didShowFakeDataWarning: boolean;\n  _didValidateConfig: boolean;\n\n  constructor(props: Tp) {\n    this._didShowFakeDataWarning = false;\n    this._didValidateConfig = false;\n    this._unresolvedProps = props;\n  }\n\n  static getFragment(fragmentName: string, variableMapping): any {\n    return Relay.Mutation.getFragment.call(\n      this,\n      fragmentName,\n      variableMapping\n    );\n  }\n\n  bindEnvironment(environment: Relay.Environment): void {\n    Relay.Mutation.prototype.bindEnvironment.call(this, environment);\n  }\n\n  getSubscription(): RelayConcreteNode {\n    invariant(\n      false,\n      '%s: Expected abstract method `getSubscription` to be implemented',\n      this.constructor.name\n    );\n  }\n\n  getConfigs(): Array<MutationConfig> {\n    invariant(\n      false,\n      '%s: Expected abstract method `getConfigs` to be implemented',\n      this.constructor.name\n    );\n  }\n\n  getVariables(): Variables {\n    invariant(\n      false,\n      '%s: Expected abstract method `getVariables` to be implemented',\n      this.constructor.name\n    );\n  }\n\n  _resolveProps(): void {\n    Relay.Mutation.prototype._resolveProps.call(this);\n  }\n}\n"
  },
  {
    "path": "src/SubscriptionRequest.js",
    "content": "/* @flow */\n\nimport printQuery from 'react-relay/lib/printRelayQuery';\nimport RelayQuery from 'react-relay/lib/RelayQuery';\n\nimport type {\n  PrintedQuery,\n  SubscriptionRequestObserver,\n  SubscriptionResult,\n  Variables,\n} from './types';\n\nexport default class SubscriptionRequest {\n  _printedQuery: ?PrintedQuery;\n  _subscription: RelayQuery.Subscription;\n  _observer: SubscriptionRequestObserver;\n\n  constructor(\n    subscription: RelayQuery.Subscription,\n    observer: SubscriptionRequestObserver,\n  ) {\n    this._printedQuery = null;\n    this._subscription = subscription;\n    this._observer = observer;\n  }\n\n  getDebugName(): string {\n    return this._subscription.getName();\n  }\n\n  getVariables(): Variables {\n    return this._getPrintedQuery().variables;\n  }\n\n  getQueryString(): string {\n    return this._getPrintedQuery().text;\n  }\n\n  _getPrintedQuery(): PrintedQuery {\n    if (!this._printedQuery) {\n      this._printedQuery = printQuery(this._subscription);\n    }\n\n    return this._printedQuery;\n  }\n\n  getClientSubscriptionId(): string {\n    return this._subscription.getVariables().input.clientSubscriptionId;\n  }\n\n  onNext(payload: SubscriptionResult) {\n    this._observer.onNext(payload);\n  }\n\n  onError(error: any) {\n    this._observer.onError(error);\n  }\n\n  onCompleted(value: any) {\n    this._observer.onCompleted(value);\n  }\n}\n"
  },
  {
    "path": "src/__tests__/createContainer.js",
    "content": "jest.mock('react-relay/lib/RelayContainer', () => ({\n  create: (Component) => {\n    Component.isRelayContainer = true; // eslint-disable-line no-param-reassign\n    return Component;\n  },\n}));\n\nimport { mount } from 'enzyme';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nimport RelaySubscriptions from '..';\n\ndescribe('createContainer', () => {\n  it('should support relay.subscribe', () => {\n    const environment = new RelaySubscriptions.Environment();\n    spyOn(environment, 'startSubscription');\n\n    const dummySubscription = new RelaySubscriptions.Subscription();\n\n    class Widget extends React.Component {\n      static propTypes = {\n        relay: PropTypes.object.isRequired,\n      };\n\n      componentDidMount() {\n        this.props.relay.subscribe(dummySubscription);\n      }\n\n      render() {\n        return null;\n      }\n    }\n\n    const WidgetContainer = RelaySubscriptions.createContainer(Widget, {});\n    expect(WidgetContainer.isRelayContainer).toBe(true);\n\n    mount(<WidgetContainer relay={{}} />, {\n      context: {\n        relay: {\n          environment,\n          variables: {},\n        },\n      },\n    });\n    expect(environment.startSubscription).toHaveBeenCalledWith(dummySubscription);\n  });\n});\n"
  },
  {
    "path": "src/createContainer.js",
    "content": "/* @flow */\n\nimport isEqual from 'lodash/isEqual';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Relay from 'react-relay/classic';\nimport type { RelayContainerSpec } from 'react-relay/lib/RelayContainer';\n\nimport type Subscription from './Subscription';\nimport type { SubscriptionDisposable } from './types';\n\ntype subscriptionFn = (props: Object) => ?Subscription<any>;\n\ntype ActiveSubscription = {\n  subscription: Subscription<any>,\n  disposable: SubscriptionDisposable,\n}\n\nfunction disposeActiveSubscription(activeSubscription) {\n  if (!activeSubscription) {\n    return;\n  }\n\n  activeSubscription.disposable.dispose();\n}\n\nfunction subscribe(\n  Component: ReactClass<any>,\n  subscriptionsSpec: ?Array<subscriptionFn>,\n) {\n  const componentName = Component.displayName || Component.name || 'Component';\n\n  return class Subscribe extends React.Component {\n    static displayName = `Subscribe(${componentName})`;\n\n    static propTypes = {\n      relay: PropTypes.object.isRequired,\n    };\n\n    static contextTypes = {\n      relay: Relay.PropTypes.ClassicRelay,\n    };\n\n    relayProp: mixed;\n    activeSubscriptions: Array<?ActiveSubscription>;\n\n    constructor(props, context) {\n      super(props, context);\n\n      this.relayProp = this.makeRelayProp(props);\n      this.activeSubscriptions = [];\n    }\n\n    componentDidMount() {\n      if (subscriptionsSpec) {\n        subscriptionsSpec.forEach((createSubscription) => {\n          this.activeSubscriptions.push(\n            this.makeActiveSubscription(createSubscription(this.props)),\n          );\n        });\n      }\n    }\n\n    componentWillReceiveProps(nextProps) {\n      if (nextProps.relay !== this.props.relay) {\n        this.relayProp = this.makeRelayProp(nextProps);\n      }\n\n      if (subscriptionsSpec) {\n        subscriptionsSpec.forEach((createSubscription, index) => {\n          const activeSubscription = this.activeSubscriptions[index];\n          const nextSubscription = createSubscription(nextProps);\n\n          if (!this.areSubscriptionsEqual(\n            activeSubscription,\n            nextSubscription,\n          )) {\n            disposeActiveSubscription(activeSubscription);\n            this.activeSubscriptions[index] =\n              this.makeActiveSubscription(nextSubscription);\n          }\n        });\n      }\n    }\n\n    componentWillUnmount() {\n      if (subscriptionsSpec) {\n        this.activeSubscriptions.forEach(disposeActiveSubscription);\n      }\n    }\n\n    makeRelayProp(props) {\n      return {\n        ...props.relay,\n        subscribe: this.context.relay.environment.startSubscription,\n      };\n    }\n\n    makeActiveSubscription(subscription) {\n      if (!subscription) {\n        return null;\n      }\n\n      return {\n        subscription,\n        disposable: this.context.relay.environment.startSubscription(subscription),\n      };\n    }\n\n    areSubscriptionsEqual(activeSubscription, nextSubscription) {\n      if (!nextSubscription && !activeSubscription) {\n        // Both old and new are falsy.\n        return true;\n      }\n\n      if (!nextSubscription || !activeSubscription) {\n        // Only one of the pair is falsy.\n        return false;\n      }\n\n      const subscription = activeSubscription.subscription;\n\n      if (nextSubscription.constructor !== subscription.constructor) {\n        // Subscriptions are of different types.\n        return false;\n      }\n\n      // Need to bind subscription to Relay environment to get variables.\n      nextSubscription.bindEnvironment(this.context.relay.environment);\n\n      // Check if variables match.\n      return isEqual(\n        nextSubscription.getVariables(),\n        subscription.getVariables(),\n      );\n    }\n\n    render() {\n      return (\n        <Component\n          {...this.props}\n          relay={this.relayProp}\n        />\n      );\n    }\n  };\n}\n\nexport default function createContainer(\n  Component: ReactClass<any>,\n  spec: RelayContainerSpec & { subscriptions?: subscriptionFn[] },\n) {\n  return Relay.createContainer(\n    subscribe(Component, spec.subscriptions),\n    spec\n  );\n}\n"
  },
  {
    "path": "src/createSubscriptionQuery.js",
    "content": "/* @flow */\n\nimport RelayMetaRoute from 'react-relay/lib/RelayMetaRoute';\nimport RelayQuery from 'react-relay/lib/RelayQuery';\n\nimport type { RelayConcreteNode, Variables } from './types';\n\nexport default function createSubscriptionQuery(\n  concreteNode: RelayConcreteNode,\n  variables: Variables\n): RelayQuery.Subscription {\n  return RelayQuery.Subscription.create(\n    concreteNode,\n    RelayMetaRoute.get('$createSubscriptionQuery'),\n    variables\n  );\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "/* @flow */\n\nimport createContainer from './createContainer';\nimport Environment from './Environment';\nimport Subscription from './Subscription';\n\nexport default { createContainer, Environment, Subscription };\nexport { createContainer, Environment, Subscription };\n"
  },
  {
    "path": "src/types.js",
    "content": "/* @flow */\n\nexport type Variables = {[name: string]: mixed};\nexport type RelayConcreteNode = {[name: string]: mixed};\n\nexport type MutationConfig = {\n  type: string;\n  [name: string]: mixed;\n};\n\nexport type PrintedQuery = {\n  text: string;\n  variables: Variables;\n};\n\nexport type SubscriptionResult = {[name: string]: mixed};\n\nexport type SubscriptionObserver = {\n  onNext?: (value: SubscriptionResult) => void;\n  onError?: (error: any) => void;\n  onCompleted?: (value: any) => void;\n}\n\nexport type SubscriptionRequestObserver = {\n  onNext: (value: SubscriptionResult) => void;\n  onError: (error: any) => void;\n  onCompleted: (value: any) => void;\n}\n\nexport type SubscriptionDisposable = {\n  dispose: () => void;\n}\n"
  },
  {
    "path": "src/updateStoreData.js",
    "content": "/* @flow */\n\nimport type Relay from 'react-relay/classic';\nimport type RelayQuery from 'react-relay/lib/RelayQuery';\n\nimport type {\n  MutationConfig,\n  SubscriptionResult,\n} from './types';\n\nexport default function updateStoreData(\n  environment: Relay.Environment,\n  configs: Array<MutationConfig>,\n  query: RelayQuery.Operation,\n  payload: SubscriptionResult\n) {\n  const storeData = environment.getStoreData();\n  const payloadName = query.getCall().name;\n\n  // FIXME: Applying a RANGE_ADD update requires a clientMutationId. This is a\n  // nonce that won't collide with any actual mutation IDs.\n  const clientMutationId = Math.random().toString(36);\n\n  storeData.handleUpdatePayload(\n    query,\n    { ...payload[payloadName], clientMutationId },\n    { configs }\n  );\n}\n"
  }
]