{"id":13499549,"url":"https://github.com/oliyh/re-graph","last_synced_at":"2025-04-06T19:11:30.036Z","repository":{"id":37768409,"uuid":"107311594","full_name":"oliyh/re-graph","owner":"oliyh","description":"A graphql client for clojurescript and clojure","archived":false,"fork":false,"pushed_at":"2025-02-20T23:19:13.000Z","size":227,"stargazers_count":464,"open_issues_count":15,"forks_count":39,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-30T18:08:58.190Z","etag":null,"topics":["client","clj","cljs","clojure","clojurescript","graphql","graphql-client","re-frame"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oliyh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":["oliyh"]}},"created_at":"2017-10-17T18:59:09.000Z","updated_at":"2025-03-16T16:05:03.000Z","dependencies_parsed_at":"2024-01-09T15:15:11.566Z","dependency_job_id":null,"html_url":"https://github.com/oliyh/re-graph","commit_stats":{"total_commits":143,"total_committers":19,"mean_commits":7.526315789473684,"dds":"0.18181818181818177","last_synced_commit":"abffdc040461ffe1cac540d2dfce987127707afe"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliyh%2Fre-graph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliyh%2Fre-graph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliyh%2Fre-graph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliyh%2Fre-graph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oliyh","download_url":"https://codeload.github.com/oliyh/re-graph/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247535517,"owners_count":20954576,"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":["client","clj","cljs","clojure","clojurescript","graphql","graphql-client","re-frame"],"created_at":"2024-07-31T22:00:34.717Z","updated_at":"2025-04-06T19:11:29.993Z","avatar_url":"https://github.com/oliyh.png","language":"Clojure","readme":"# re-graph\n\nre-graph is a graphql client for Clojure and ClojureScript with bindings for [re-frame](https://github.com/Day8/re-frame) applications.\n\n## Upgrade notice\n\n:fire: Version `0.2.0` was recently released with breaking API changes. Please read the [Upgrade](UPGRADE.md) guide for more information.\n\n## Notes\n\nThis library behaves like the popular [Apollo client](https://github.com/apollographql/subscriptions-transport-ws)\nfor graphql and as such is compatible with [lacinia-pedestal](https://github.com/walmartlabs/lacinia-pedestal).\n\nFeatures include:\n* Subscriptions, queries and mutations\n* Supports websocket and HTTP transports\n* Works with Apollo-compatible servers like lacinia-pedestal\n* Queues websocket messages until ready\n* Websocket reconnects on disconnect\n* Simultaneous connection to multiple GraphQL services\n* Handles reauthentication without disruption\n\n## Contents\n- [Usage](#usage)\n  - [Vanilla Clojure/Script](#vanilla-clojurescript)\n  - [re-frame](#re-frame-users)\n- [Options](#options)\n- [Multiple instances](#multiple-instances)\n- [Authentication](#authentication)\n  - [Headers](#headers)\n  - [Connection init payload](#connection-init-payload)\n  - [Cookies](#cookies)\n  - [Token in query param](#token-in-query-param)\n  - [Basic auth](#basic-auth)\n  - [Sub-protocol hack](#sub-protocol-hack)\n- [Re-initialisation](#re-initialisation)\n- [Development](#development)\n\n## Usage\n\nAdd re-graph to your project's dependencies:\n\n[![Clojars Project](https://img.shields.io/clojars/v/re-graph.svg)](https://clojars.org/re-graph)\n\nThis will also pull in `re-graph.hato`, a library for using re-graph on the JVM based on\n[hato](https://github.com/gnarroway/hato) which requires JDK11.\nTo use earlier JDKs, exclude `re-graph.hato` and include `re-graph.clj-http-gniazdo`.\n\nIf you are only targeting Javascript you do not need either of these libraries.\n\n[![Clojars Project](https://img.shields.io/clojars/v/re-graph.hato.svg)](https://clojars.org/re-graph.hato)\n[![Clojars Project](https://img.shields.io/clojars/v/re-graph.clj-http-gniazdo.svg)](https://clojars.org/re-graph.clj-http-gniazdo)\n\n```clj\n;; For JDK 11+\n[re-graph \"x.y.z\"]\n\n;; For JDK 10-\n[re-graph \"x.y.z\" :exclusions [re-graph.hato]]\n[re-graph.clj-http-gniazdo \"x.y.z\"]\n\n;; For Javascript only\n[re-graph \"x.y.z\" :exclusions [re-graph.hato]]\n```\n\n### Vanilla Clojure/Script\n\nCall the `init` function to bootstrap it and then use `subscribe`, `unsubscribe`, `query` and `mutate` functions:\n\n```clojure\n(require '[re-graph.core :as re-graph])\n\n;; initialise re-graph, possibly including configuration options (see below)\n(re-graph/init {})\n\n(defn on-thing [{:keys [data errors] :as response}]\n  ;; do things with data\n))\n\n;; start a subscription, with responses sent to the callback-fn provided\n(re-graph/subscribe {:id        :my-subscription-id  ;; this id should uniquely identify this subscription\n                     :query     \"{ things { id } }\"  ;; your graphql query\n                     :variables {:some \"variable\"}   ;; arguments map\n                     :callback  on-thing})           ;; callback-fn when messages are recieved\n\n;; stop the subscription\n(re-graph/unsubscribe {:id :my-subscription-id})\n\n;; perform a query, with the response sent to the callback event provided\n(re-graph/query {:query     \"{ things { id } }\" ;; your graphql query\n                 :variables {:some \"variable\"}  ;; arguments map\n                 :callback  on-thing})          ;; callback event when response is recieved\n\n;; shut re-graph down when finished\n(re-graph/destroy {})\n```\n\n### re-frame users\nDispatch the `init` event to bootstrap it and then use the `:subscribe`, `:unsubscribe`, `:query` and `:mutate` events:\n\n```clojure\n(require '[re-graph.core :as re-graph]\n         '[re-frame.core :as re-frame])\n\n;; initialise re-graph, possibly including configuration options (see below)\n(re-frame/dispatch [::re-graph/init {}])\n\n(re-frame/reg-event-db\n  ::on-thing\n  [re-frame/unwrap]\n  (fn [db {:keys [response]}]\n    (let [{:keys [data errors]} response]\n      ;; do things with data e.g. write it into the re-frame database\n    )))\n\n;; start a subscription, with responses sent to the callback event provided\n(re-frame/dispatch [::re-graph/subscribe\n                    {:id        :my-subscription-id  ;; this id should uniquely identify this subscription\n                     :query     \"{ things { id } }\"  ;; your graphql query\n                     :variables {:some \"variable\"}   ;; arguments map\n                     :callback  [::on-thing]}])      ;; callback event when messages are recieved\n\n;; stop the subscription\n(re-frame/dispatch [::re-graph/unsubscribe {:id :my-subscription-id}])\n\n;; perform a query, with the response sent to the callback event provided\n(re-frame/dispatch [::re-graph/query\n                    {:id        :my-query-id         ;; unique id for this query (optional, used for deduplication)\n                     :query     \"{ things { id } }\"  ;; your graphql query\n                     :variables {:some \"variable\"}   ;; arguments map\n                     :callback  [::on-thing]}])      ;; callback event when response is recieved\n\n;; shut re-graph down when finished\n(re-frame/dispatch [::re-graph/destroy {}])\n```\n\n### Options\n\nOptions can be passed to the init event, with the following possibilities:\n\n```clojure\n(re-frame/dispatch\n  [::re-graph/init\n   {:ws {:url                     \"wss://foo.io/graphql-ws\" ;; override the websocket url (defaults to /graphql-ws, nil to disable)\n         :sub-protocol            \"graphql-ws\"              ;; override the websocket sub-protocol (defaults to \"graphql-ws\")\n         :reconnect-timeout       5000                      ;; attempt reconnect n milliseconds after disconnect (defaults to 5000, nil to disable)\n         :resume-subscriptions?   true                      ;; start existing subscriptions again when websocket is reconnected after a disconnect (defaults to true)\n         :connection-init-payload {}                        ;; the payload to send in the connection_init message, sent when a websocket connection is made (defaults to {})\n         :impl                    {}                        ;; implementation-specific options (see hato for options, defaults to {}, may be a literal or a function that returns the options)\n         :supported-operations    #{:subscribe              ;; declare the operations supported via websocket, defaults to all three\n                                    :query                  ;;   if queries/mutations must be done via http set this to #{:subscribe} only\n                                    :mutate}\n        }\n\n    :http {:url    \"http://bar.io/graphql\"   ;; override the http url (defaults to /graphql)\n           :impl   {}                        ;; implementation-specific options (see clj-http or hato for options, defaults to {}, may be a literal or a function that returns the options)\n           :supported-operations #{:query    ;; declare the operations supported via http, defaults to :query and :mutate\n                                   :mutate}\n          }\n   }])\n```\n\nEither `:ws` or `:http` can be set to nil to disable the WebSocket or HTTP protocols.\n\n### Multiple instances\n\nre-graph now supports multiple instances, allowing you to connect to multiple GraphQL services at the same time.\nAll function/event signatures now take an optional instance-name as the first argument to let you address them separately:\n\n```clojure\n(require '[re-graph.core :as re-graph])\n\n;; initialise re-graph for service A\n(re-graph/init {:instance-id :service-a\n                :ws {:url \"wss://a.com/graphql-ws}})\n\n;; initialise re-graph for service B\n(re-graph/init {:instance-id :service-b\n                :ws {:url \"wss://b.com/api/graphql-ws}})\n\n(defn on-a-thing [{:keys [data errors] :as payload}]\n  ;; do things with data from service A\n))\n\n;; subscribe to service A, events will be sent to the on-a-thing callback\n(re-graph/subscribe {:instance-id :service-a    ;; the instance-name you want to talk to\n                     :id :my-subscription-id    ;; this id should uniquely identify this subscription for this service\n                     :query \"{ things { a } }\"\n                     :callback on-a-thing})\n\n(defn on-b-thing [{:keys [data errors] :as payload}]\n  ;; do things with data from service B\n))\n\n;; subscribe to service B, events will be sent to the on-b-thing callback\n(re-graph/subscribe {:instance-id :service-b    ;; the instance-name you want to talk to\n                     :id :my-subscription-id\n                     :query \"{ things { a } }\"\n                     :callback on-b-thing})\n\n;; stop the subscriptions\n(re-graph/unsubscribe {:instance-id :service-a\n                       :id :my-subscription-id})\n(re-graph/unsubscribe {:instance-id :service-b\n                       :id :my-subscription-id})\n```\n\n## Authentication\n\nThere are several methods of authenticating with the server, with various trade-offs. Most complications relate to the websocket connection from the browser, as the usual method of providing an `Authorization` header is [not (currently) possible](https://github.com/whatwg/html/issues/3062).\n\n- [Headers](#headers)\n- [Connection init payload](#connection-init-payload)\n- [Cookies](#cookies)\n- [Token in query param](#token-in-query-param)\n- [Basic auth](#basic-auth)\n- [Sub-protocol hack](#sub-protocol-hack)\n\n### Headers\n\nThe most conventional way to authenticate is to use HTTP headers on requests to the server and include an authentication token:\n\n```clojure\n(re-frame/dispatch\n  [::re-graph/init\n    {:ws {:impl {:headers {:Authorization \"my-auth-token\"}}}\n     :http {:impl {:headers {:Authorization \"my-auth-token\"}}}}])\n```\n\nThis will work for the following cases:\n- JVM for websockets and http (using hato)\n- Browser for http only\n\nNote that it **will not** work for websocket connections from the browser, for which you will have to choose one of the other methods described below.\n\n### Connection init payload\n\nFor websocket connections, the [de-facto Apollo spec](https://www.apollographql.com/docs/graphql-subscriptions/authentication/) defines a `connection_init` message which is sent after the websocket connection has been established, but before any GraphQL traffic. This can be used to contain an authentication token which can be associated with the connection, or the connection can be terminated.\n\n```clj\n(re-frame/dispatch\n  [::re-graph/init\n    {:ws {:connection-init-payload {:token \"my-auth-token\"}}}])\n```\n\nNote that for Hasura, and possibly other Apollo server backed instances, your payload may need to look like `{:headers {:authorization (str \"Bearer \" jwt)}}`\n\n### Cookies\n\nWhen using re-graph within a browser, site cookies are shared between HTTP and WebSocket connection automatically. There's nothing special that needs to be done.\n\nWhen using re-graph with Clojure, however, some configuration is necessary to ensure that the same cookie store is used for both HTTP and WebSocket connections.\n\nBefore initialising re-graph, create a common HTTP client.\n\n```\n(ns user\n  (:require\n    [hato.client :as hc]\n    [re-graph.core :as re-graph]))\n\n(def http-client (hc/build-http-client {:cookie-policy :all}))\n```\n\nSee the [hato documentation](https://github.com/gnarroway/hato) for all the supported configuration options.\n\nWhen initialising re-graph, configure both the HTTP and WebSocket connections with this client:\n\n```\n(re-graph/init {:http {:impl {:http-client http-client}}\n                :ws   {:impl {:http-client http-client}}})\n```\n\nIn the call, you can provide any supported re-graph or hato options. Be careful though; hato convenience options for the HTTP client will be ignored when using the `:http-client` option.\n\nIf you are using lacinia, you probably need to use the `:init-context` option of the [listener-fn-factory](https://github.com/walmartlabs/lacinia-pedestal/blob/v1.1/src/com/walmartlabs/lacinia/pedestal/subscriptions.clj#L517) to be able to extract the cookie from the underlying webserver request.\n\n### Token in query param\n\nYou can put a token in the http and websocket urls and use it to authenticate when handling the request.\n\n```clj\n(re-frame/dispatch\n  [::re-graph/init\n    {:http {:url \"https://my-server.com/graphql?auth=my-auth-token\"}\n     :ws {:url \"wss://my-server.com/graphql-ws?auth=my-auth-token\"}}])\n```\n\nNote that query params may be included in the log files of the server.\n\n### Basic auth\n\nYou can put basic auth in the http and websocket urls and use it to authenticate when handling the request.\n\n```clj\n(re-frame/dispatch\n  [::re-graph/init\n    {:http {:url \"https://my-user:my-password@my-server.com/graphql\"}\n     :ws {:url \"wss://my-user:my-password@my-server.com/graphql-ws\"}}])\n```\n\n### Sub-protocol hack\n\nAs mentioned in [https://github.com/whatwg/html/issues/3062](https://github.com/whatwg/html/issues/3062) it is contentious but possible to smuggle authentication in the websocket sub-protocol, which normally describes the kind of traffic expected over the websocket (the default in re-graph is `graphql-ws`).\n\n```clojure\n(re-frame/dispatch\n  [::re-graph/init\n    {:ws {:sub-protocol \"graphql-ws;my-auth-token\"}}])\n```\n\n## Re-initialisation\n\nWhen initialising re-graph you may have included authorisation tokens e.g.\n\n```clojure\n(re-frame/dispatch [::re-graph/init {:http {:url \"http://foo.bar/graph-ql\"\n                                            :impl {:headers {\"Authorization\" 123}}}\n                                     :ws {:connection-init-payload {:token 123}}}])\n```\n\nIf those tokens expire you can refresh them using `re-init` as follows which allows you to change any parameter provided to re-graph:\n\n```clojure\n(re-frame/dispatch [::re-graph/re-init {:http {:impl {:headers {\"Authorization\" 456}}}\n                                        :ws {:connection-init-payload {:token 456}}}])\n```\n\nThe `connection-init-payload` will be sent again and all future remote calls will contain the updated parameters.\n\n## Development\n\n`cider-jack-in-clj\u0026cljs`\n\nCLJS tests are available at http://localhost:9500/figwheel-extra-main/auto-testing\nYou will need to run `(re-graph.integration-server/start!)` for the integration tests to pass.\n\n[![CircleCI](https://circleci.com/gh/oliyh/re-graph.svg?style=svg)](https://circleci.com/gh/oliyh/re-graph)\n\n## License\n\nCopyright © 2017 oliyh\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","funding_links":["https://github.com/sponsors/oliyh"],"categories":["Libraries","Clojure","Implementations"],"sub_categories":["ClojureScript Libraries","ClojureScript"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliyh%2Fre-graph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foliyh%2Fre-graph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliyh%2Fre-graph/lists"}