{"id":21076548,"url":"https://github.com/monojack/blips","last_synced_at":"2025-05-16T07:31:33.182Z","repository":{"id":97051014,"uuid":"109500440","full_name":"monojack/blips","owner":"monojack","description":"State management for the GraphQL heads","archived":false,"fork":false,"pushed_at":"2017-12-12T21:16:13.000Z","size":915,"stargazers_count":45,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-12T04:45:45.855Z","etag":null,"topics":["application-state","graphql","javascript"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monojack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-04T14:14:38.000Z","updated_at":"2024-03-29T04:13:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"1637d03b-3412-407b-b15b-edc779ae70e9","html_url":"https://github.com/monojack/blips","commit_stats":{"total_commits":104,"total_committers":2,"mean_commits":52.0,"dds":0.009615384615384581,"last_synced_commit":"a6414b9464899d5e7a7ac38c32c647daee1e6e16"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fblips","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fblips/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fblips/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fblips/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monojack","download_url":"https://codeload.github.com/monojack/blips/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254488228,"owners_count":22079385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["application-state","graphql","javascript"],"created_at":"2024-11-19T19:29:00.412Z","updated_at":"2025-05-16T07:31:32.329Z","avatar_url":"https://github.com/monojack.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003ca href='https://github.com/monojack/blips'\u003e\n\n\u003cimg src='https://raw.githubusercontent.com/monojack/blips/master/logo/blips-logo-dark-text.png' height='100'\u003e\n\n\u003c/a\u003e\n\nClient for managing application state with GraphQL operations.\n\n[![Build Status](https://travis-ci.org/monojack/blips.svg?branch=master)](https://travis-ci.org/monojack/blips)\n[![npm version](https://img.shields.io/npm/v/blips.svg)](https://www.npmjs.com/package/blips)\n[![npm downloads](https://img.shields.io/npm/dm/blips.svg)](https://www.npmjs.com/package/blips)\n\n## Table of contents\n\n* [The concept](#the-concept)\n* [The why?](#the-why)\n* [The basics](#the-basics)\n  * [installation](#installation)\n  * [the client instance](#the-client-instance)\n  * [operations and execution](#operations-and-execution)\n  * [subscriptions](#subscriptions)\n  * [fetching data](#fetching-data)\n  * [extending the context](#extending-the-context)\n  * [use with React](#use-with-react)\n* [The tips](#the-tips)\n* [The kudos](#the-kudos)\n\n## The concept\n\n**Blips** exposes a simple interface for managing your application state with\nGraphQL.\n\nThe state is contained inside a single _store_ object. It can only be changed\nthrough **_mutations_**, you can read from it through **_queries_** and can also\nlisten for changes with **_subscriptions_**.\n\n## The why?\n\nI developed **Blips** because I wanted to use **GraphQL** with every project,\nregardless of what the API server looks like. So if my application is consuming\na simple API (whatever kind), I can still have a _store_ that manages the\napplication state and write queries/mutations that would resolve with making\nrequests to that API.\n\n## The basics\n\nThe following examples assume some familiarity with GraphQL. If you haven't used\nit before, or you have no idea about what it is, you should visit graphql.org\nand read about GraphQL in detail.\n\n### Installation\n\n```bash\nnpm install blips graphql\n```\n\n### The client instance\n\n\u003e new BlipsClient({ typeDefs [, resolvers] } [, initialState] [, config] )\n\nCreating a client requires type definitions, optional resolvers, optional\ninitial state and an optional configuration object.\n\n##### typeDefs\n\n```js\n// types.js\n\nexport default `\n  type Todo {\n    id: String!\n    label: String!\n    completed: Boolean!\n  }\n\n  type Query {\n    allTodos: [Todo]!\n    todo(id: String!): Todo!\n  }\n\n  type Mutation {\n    createTodo(id: String!, label: String!, completed: Boolean)\n  }\n`\n```\n\nThe `types.js` file contains 3 definitions:\n\n* a basic `Todo` object type containing three fields: `id`, `label`,\n  `completed`.\n* the root `Query` type\n* the root `Mutation` type\n\nThe `!` marks the field as required. See\n[graphql.org](http://graphql.org/learn/schema/#type-system) to read more about\nthe type system.\n\n##### resolvers\n\n```js\n// resolvers.js\n\nexport default {\n  Query: {\n    allTodos: (obj, args, { store }) =\u003e store.state.todos || [],\n  },\n  Mutation: {\n    createTodo: (\n      obj,\n      { id, label, completed = false },\n      { store: { state: { todos } } }\n    ) =\u003e {\n      const newTodo = { id, label, completed }\n      todos = [...todos, newTodo]\n      return newTodo\n    },\n  },\n}\n```\n\nThe `resolvers.js` file provides definitions for `allTodos` query and the\n`createTodo` mutation. Each resolver function accepts three arguments:\n\n* `obj`: The previous object.\n* `args`: The arguments provided to the field in the GraphQL query.\n* `context`: Provides access to important information like the currently logged\n  in user, or the store itself.\n\nSee [graphql.org](http://graphql.org/learn/execution/#root-fields-resolvers) to\nread more about resolvers.\n\n##### client\n\n```js\n// index.js\n\nimport { BlipsClient } from 'blips'\nimport resolvers from './resolvers'\nimport typeDefs from './types'\n\nconst initialState = {\n  todos: {\n    '3c4a086e-2151-4b54-acb2-13044ea553c1': {\n      id: '3c4a086e-2151-4b54-acb2-13044ea553c1',\n      label: 'Buy milk',\n      completed: false,\n    },\n  },\n}\nconst schemaDef = { typeDefs, resolvers }\n\n// new BlipsClient({ typeDefs [, resolvers] } [, initialState] [, config] )\nconst client = new BlipsClient(schemaDef, initialState)\n```\n\nYou create a new client by providing your schema definitions (`{ typeDefs,\nresolvers }`) and an optional initial state. In addition to the `schemaDefs` and\n`initialState`, you can also privide a `configuration` object as the last and\noptional argument. The `configuration` parameter is used for tapping into the\ninitial setup to define default variables or context properties and to configure\nthe `fetch` method.\n\nThe client object has the following API:\n\n* `state`: getter for your entire state.\n* `schema`: getter for your generated schema.\n  [state-clerk](https://github.com/monojack/state-clerk).\n* `query`: method for executing queries.\n* `mutate`: method for executing mutations.\n* `subscribe`: method for registering subscriptions.\n* `fetch`: method for sending queries to a real GraphQL API.\n\n### Operations and Execution\n\nYou define operations in the form of GraphQL queries, mutations or subscriptions\nand use the client methods for executing them.\n\n#### operations\n\n```js\n// operations.js\n\nexport const todoQuery = `\n  query todoQuery {\n    todo {\n      label\n      completed\n    }\n  }\n`\n\nexport const createTodoMutation = `\n  mutation createTodoMutation($label: String!, $completed: Boolean) {\n    createTodo(label: $label, completed: $completed) {\n      id\n      label\n      completed\n    }\n  }\n`\n```\n\nRead more about [queries and mutations](http://graphql.org/learn/queries)\n\n#### execution\n\n```js\nimport { todoQuery, createTodoMutation } from './operations'\n\nclient\n  .query(todoQuery, {\n    variables: { id: '3c4a086e-2151-4b54-acb2-13044ea553c1' },\n  })\n  .then(res =\u003e {\n    console.log(res)\n    // {\n    //   data: {\n    //     todo: {\n    //       label: 'Buy milk',\n    //       completed: false\n    //     }\n    //   }\n    // }\n  })\n\nclient\n  .mutate(createTodoMutation, {\n    variables: {\n      id: '4ecca858-67f8-491e-94cc-48b262061819',\n      label: 'Learn Blips',\n    },\n  })\n  .then(res =\u003e {\n    console.log(res)\n    // {\n    //   data: {\n    //     createTodo: {\n    //       id: '4ecca858-67f8-491e-94cc-48b262061819',\n    //       label: 'Learn Blips',\n    //       completed: false\n    //     }\n    //   }\n    // }\n  })\n```\n\nIf you've used Redux before you can think of execution methods as dispatchers.\nAll executors may receive three arguments:\n\n* `operation`: can be in the form of source or documentAST\n* `options`: object containing:\n  * `variables`: the operation variables\n  * `context`: object containing any additional data that you want to pass to\n    the resolvers, like the currently logged in user, tokens etc. (This will\n    extend the default context)\n\nAll executors return promises that eventually resolve with an object containing\na `data` prop which holds the requested data. If, for some reason, the operation\nwas not successful, the `data` prop will hold an `error` object instead of the\nrequested field.\n\nManaging your state in an asynchronous way might seem scary but unless you're\nactually fetching data from a web service, hitting an API or performing database\nqueries, your data will be available with the next tick. This behaviour also\ncomes with a few benefits:\n\n* it forces you to introduce checks and error handlers which will eventually\n  result in better code.\n* you can write abstractions or helpers for managing your data and they will\n  apply everywhere.\n* it's much easier if you decide to switch from caching some of your state\n  locally to exclusively fetching it from an external service.\n\nPlus, using `async/await` will still make your code look synchronous and badass.\n\n### Subscriptions\n\n1. [GraphQL subscriptions\n   RFC](https://github.com/facebook/graphql/blob/master/rfcs/Subscriptions.md)\n2. [Proposal for GraphQL Subscriptions by\n   Apollo](https://dev-blog.apollodata.com/a-proposal-for-graphql-subscriptions-1d89b1934c18)\n\nIn addition to polling queries or scheduling them to execute at different\nmoments throughout your application life-cycle to keep your data up-to-date, you\ncan also subscribe to changes through GraphQL subscriptions:\n\n```js\nconst allTodosSubscription = `\n  subscription allTodosSubscription {\n    allTodos {\n      id\n      label\n      completed\n    }\n  }\n`\n\nconst asyncIterator = await client.subscribe(allTodosSubscription)\n\n/* Either use for-await-of */\ntry {\n  for await (const tick of asyncIterator) {\n    console.log(tick);\n    // { data: { allTodos: [ ... ] } }\n  }\n} catch (e) {\n  // { data: { error: { ... } } }\n}\n\n\n/* or transform the iterator into an observable and subscribe to it */\nconst sub = asyncIterator.toObservable().subscribe(\n  next =\u003e {\n    // { data: { allTodos: [ ... ] } }\n  },\n  error =\u003e {\n    // { data: { error: { ... } } }\n  }\n)\n\n// remember to dispose of any unused subscriptions\n// sub.unsubscribe()\n```\n\n**Blips** uses [Apollo's\ngraphql-subscriptions](https://github.com/apollographql/graphql-subscriptions)\n**PubSub** implementation, where any query or mutation would `publish` data over\na specific topic while your subscription resolvers subscribe to one or more\ntopics.\n\nIn order to use subscriptions with **Blips**, you need to have access to the\nclient's `PubSub` instance. We can achieve that by passing a `resolvers`\nfunction instead of an object when creating the client instance. This function\naccepts as first argument an object containing the `PubSub` instance and the\n[`withFilter`](https://github.com/apollographql/graphql-subscriptions#filters)\nmethod.\n\n#### simple subscriptions\n\n```js\n// resolvers.js\n\nexport default ({ pubsub, withFilter }) =\u003e ({\n  Query: { ... },\n  Subscription: {\n    allTodos: {\n      resolve: (obj, args, { store }) =\u003e store.state.todos || [],\n      subscribe: () =\u003e pubsub.asyncIterator([ 'TODO_UPDATED', 'TODO_CREATED', 'TODO_DELETED', ]),\n    }\n  }\n  Mutation: {\n    createTodo: (obj, { id, label, completed = false }, { store: { state: { todos } } }) =\u003e {\n      const newTodo = { id, label, completed }\n      todos = [ ...todos, newTodo ]\n\n      pubsub.publish('TODO_CREATED')\n      return newTodo\n    }\n  }\n})\n```\n\nIn the above example, the `createTodoMutation` publishes over `TODO_CREATED` as\nsoon as the todos get updated with the new entry. It doesn't need to publish any\ndata because the `allTodos` subscription will return all the todos and it's\n`resolve` method will do just that.\n\n#### filters\n\n\u003e When publishing data to subscribers, we need to make sure that each\n\u003e subscribers get only the data it needs.\n\n\u003e To do so, we can use withFilter helper from this package, which wraps\n\u003e AsyncIterator with a filter function, and let you control each publication for\n\u003e each user.\n\n```js\n// types.js\n\nexport default {\n  ...\n  type Subscription {\n    todo(id: String!): Todo!\n  }\n  ...\n}\n\n// resolvers.js\n\nexport default ({pubsub, withFilter}) =\u003e ({\n  Query: { ... },\n  Subscription: {\n    todo: {\n      subscribe: withFilter(\n        () =\u003e pubsub.asyncIterator('TODO_UPDATED'),\n        (payload, variables) =\u003e payload \u0026\u0026 payload.todo.id === variables.id\n      )\n    }\n  },\n  Mutation: {\n    updateTodo: (obj, { id, label, completed }, { store }) =\u003e {\n      const modified = store.patch(id, {\n        label,\n        completed,\n      })('todos')\n      pubsub.publish('TODO_UPDATED', { todo: modified })\n      return modified\n    }\n  }\n})\n```\n\nThe `updateTodoMutation` will publish the updated todo over the `TODO_UPDATED`\ntopic. If we have subscriptions registered for each todo, only those who's ID\nvariable match to the updated todo's ID will emit.\n\nHead over to\n[graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions)\nfor a more detailed documentation on how to use this PubSub implementation.\n\n### Fetching data\n\nYour access is not limited to the default context, nor limited to working with\nonly the local state. Resolvers can completely ignore any context and just make\nasync requests to external API servers.\n\n#### REST API\n\n```js\nconst resolvers = {\n  Mutation: {\n    createTodo: async () =\u003e\n      await fetch('api/v1/todos', {\n        method: 'post',\n        body: JSON.stringify(args)\n      }).then(res =\u003e res.json())\n  }\n}\n\nconst createTodoMutation = `\n  mutation createTodoMutation($label: String!, completed: Boolean) {\n    createTodo(label: $label, completed) {\n      id\n      label\n      completed\n    }\n  }\n`\n\nconst newTodo = await client.mutate(createTodoMutation, { variables: { label: 'Buy milk' } })\n```\n\n#### GraphQL API\n\n```js\nimport { BlipsClient } from 'blips'\nimport resolvers from './resolvers'\nimport typeDefs from './types'\n\nconst initialState = {}\n\nconst client = new BlipsClient({ typeDefs, resolvers }, initialState, { fetch: { uri: 'http://localhost:3000/graphql' } })\n\nconst allTodosQuery = `\n  query allTodosQuery($first: Int) {\n    allTodos(first: $first) {\n      id\n      label\n      completed\n    }\n  }\n`\n// client.fetch(query [, options] [, operationName] )\nconst todos = await client.fetch(allTodosQuery, { variables: { first: 10 } })\n```\n\n### Extending the context\n\nYou can extend the context provided to the resolvers in two ways:\n\n**1.** When creating the client instance, by passing a `context` object through\nthe `options` argument. This will extend the default context will be available\nto all your resolvers:\n\n```js\nconst client = new BlipsClient({ typeDefs, resolvers }, initialState, {\n  context: { foo: 'bar' },\n})\n```\n\n**2.** Through the `options` argument of every executor, which will make it\navailable only for that execution context:\n\n\u003e `client.query(allBookmarksQuery, { context: { user: loggedUser } })`\n\nexample:\n\n```js\n// types\nconst typeDefs = `\n  type Bookmarks {\n    id: Int!,\n    post: Post!,\n    user: User!\n  }\n\n  type Query {\n    allBookmarks: [Bookmark]!\n  }\n`\n\n// resolvers\nconst resolvers = {\n  Query: {\n    allBookmarks: (object, args, { store, user }) =\u003e {\n      // using identifier { user_id: user.id } to return only the current user's bookmarks\n      return store.get('bookmarks', { user_id: user.id })\n      // Returns:\n      // [\n      //   { id: 1, post_id: 11, user_id: 23 },\n      //   { id: 3, post_id: 155, user_id: 23 }\n      // ]\n    },\n  },\n}\n\n// initial state\nconst initialState = {\n  bookmarks: [\n    { id: 1, post_id: 11, user_id: 23 },\n    { id: 2, post_id: 356, user_id: 77 },\n    { id: 3, post_id: 155, user_id: 23 },\n  ],\n}\n\nconst client = new BlipsClient({ typeDefs, resolvers }, initialState)\n\nconst allBookmarksQuery = `\n  query allBookmarksQuery {\n    allBookmarks {\n      id\n      post\n      user\n    }\n  }\n`\nconst myBookmarks = client.query(allBookmarksQuery, {\n  context: { user: loggedUser },\n})\n```\n\n### Use with React\n\nThis _basics_ guide contains examples of using **Blips** with vanilla JS. The\npreferred way to use with **React** is with\n[react-blips](https://github.com/monojack/react-blips). Read the documentation\nthere and/or check out the `*-react-blips`\n[examples](https://github.com/monojack/blips/tree/master/examples) to get an\nidea of how it works.\n\n## The tips\n\n### [dataloader](https://github.com/facebook/dataloader)\n\nSince this is `GraphQL`, some of your operations may result in multiple queries\nfor the same resource. Imagine the following scenario:\n\n```js\nconst state = {\n  comments: {\n    '3c4a086e-2151-4b54-acb2-13044ea553c1': {\n      id: '3c4a086e-2151-4b54-acb2-13044ea553c1',\n      message: 'Nice job!',\n      created: 1510737448000,\n      user_id: 'b1e7ed4d-7baa-4209-8ef3-0ccea2b420b0'\n    },\n    'f0ff08d2-9ea4-48b3-a77f-6b1517b5c827': {\n      id: 'f0ff08d2-9ea4-48b3-a77f-6b1517b5c827',\n      message: 'Great read!',\n      created: 1510737617221,\n      user_id: 'b1e7ed4d-7baa-4209-8ef3-0ccea2b420b0'\n    },\n  },\n  users: {\n    'b1e7ed4d-7baa-4209-8ef3-0ccea2b420b0'; {\n      id: 'b1e7ed4d-7baa-4209-8ef3-0ccea2b420b0',\n      firstName: 'John',\n      lastName: 'Doe',\n      comments: ['3c4a086e-2151-4b54-acb2-13044ea553c1', 'f0ff08d2-9ea4-48b3-a77f-6b1517b5c827']\n      // ...\n    }\n  }\n}\n\nconst types = `\n  ...\n  type Comment {\n    id: String!\n    message: String!\n    user: User!\n    created: Int!\n  }\n  ...\n`\n\nconst resolvers = {\n  // ...\n  Query: {\n    userCommentsQuery: (obj, { uid }, { store }) =\u003e store.get('comments', { user_id: uid }),\n  },\n\n  Comment: {\n    user: ({ user_id }, args, { store }) =\u003e store.get('users', user_id),\n  },\n  // ...\n}\n\nconst userCommentsQuery = `\n  query userCommentsQuery($uid: String!) {\n    userComments(uid: $uid) {\n      idea\n      message\n      created\n      user {\n        id\n        firstName\n        photo\n      }\n    }\n  }\n`\n```\n\nWe have a `comments` collection where each of the comments contain a `user_id`\nfield that is a reference to it's poster. When `userCommentsQuery` is executed,\nthe `user` field resolver for the comment will be called twice, even though it\nwill return the same user. That is bad design for a graphql _server_ (querying\nthe database multiple times for the same resource), but it doesn't apply when\nmanaging the client state. If you were to not add a resolver for the `Comment`'s\n`user` field, you would get the list of comments and then map over it to expand\nthe user data. That would still result in querying the store for the same\nresource multiple times.\n\nNow, because **Blips** manages all state asynchronously, we can use\n**dataloader** to batch and cache our resolvers, and that is a nice win and\nanother benefit of managing your state asynchronously!\n\n### [graphql-tag](https://www.apollographql.com/docs/react/recipes/webpack.html)\n\nWith `graphql-tag` you can write your queries and type definitions in\n`.graphql|gql` files rather than `.js` that export template literals. All you\nneed to do is [add the loader to your webpack\nconfig](https://www.apollographql.com/docs/react/recipes/webpack.html).\n\n### mergers\n\nYou can further split your queries and/or type definitions in multiple modules\nand use a package that will merge them before creating the client instance. See\n[merge-graphql-schemas](https://github.com/okgrow/merge-graphql-schemas) or\n[gql-merge](https://www.npmjs.com/package/gql-merge)\n\n## The kudos\n\n* Props to the awesome developers at Facebook for giving us\n  [GraphQL](http://graphql.org/)\n* Props to the guys at [Apollo](https://www.apollographql.com/) for their\n  awesome work. Using `apollo-client` is what inspired me to start working on\n  this library, and they also provide some of the tools it depends on.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fblips","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonojack%2Fblips","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fblips/lists"}