{"id":13829732,"url":"https://github.com/den1k/subgraph","last_synced_at":"2025-05-15T13:32:24.247Z","repository":{"id":94949996,"uuid":"97553612","full_name":"den1k/subgraph","owner":"den1k","description":"Reactive graph database for re-frame","archived":false,"fork":false,"pushed_at":"2017-08-04T02:12:10.000Z","size":85,"stargazers_count":79,"open_issues_count":8,"forks_count":2,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-12T23:14:22.758Z","etag":null,"topics":["graph-database","re-frame"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/den1k.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-07-18T04:45:29.000Z","updated_at":"2025-02-10T09:42:20.000Z","dependencies_parsed_at":"2023-04-17T10:54:21.602Z","dependency_job_id":null,"html_url":"https://github.com/den1k/subgraph","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/den1k%2Fsubgraph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/den1k%2Fsubgraph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/den1k%2Fsubgraph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/den1k%2Fsubgraph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/den1k","download_url":"https://codeload.github.com/den1k/subgraph/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254349509,"owners_count":22056362,"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":["graph-database","re-frame"],"created_at":"2024-08-04T10:00:43.928Z","updated_at":"2025-05-15T13:32:19.211Z","avatar_url":"https://github.com/den1k.png","language":"Clojure","funding_links":[],"categories":["Tools","数据库"],"sub_categories":["Utils","Spring Cloud框架"],"readme":"# SubGraph\n\n\u003e Reactive graph database for re-frame\n\n[![CircleCI](https://circleci.com/gh/vimsical/subgraph.svg?style=shield\u0026circle-token=60839af806151dc02bcd591d9ec9a26875d0820b)](https://circleci.com/gh/vimsical/subgraph)\n\n[![Clojars Project](https://img.shields.io/clojars/v/vimsical/subgraph.svg)](https://clojars.org/vimsical/subgraph)\n\n\n\n## Releases and Dependency Information\n\n* SNAPSHOT only, stable release coming soon\n\n* [All releases](https://clojars.org/vimsical/subgraph)\n\n[Leiningen] dependency information:\n\n    [vimsical/subgraph \"0.1.0-SNAPSHOT\"]\n\n[Maven] dependency information:\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003evimsical\u003c/groupId\u003e\n      \u003cartifactId\u003esubgraph\u003c/artifactId\u003e\n      \u003cversion\u003e0.1.0-SNAPSHOT\u003c/version\u003e\n    \u003c/dependency\u003e\n\n[Gradle] dependency information:\n\n    compile \"vimsical:subgraph:0.1.0-SNAPSHOT\"\n\n[Clojars]: http://clojars.org/\n[Leiningen]: http://leiningen.org/\n[Maven]: http://maven.apache.org/\n[Gradle]: http://www.gradle.org/\n\n\n\n## Dependencies and Compatibility\n\nSubGraph is written in `.cljc` and depends on Clojure or ClojureScript version 1.8.0 or higher.\n\n`re-frame` and `reagent` are provided dependencies and will need to be included in your own project's dependencies.\n\n\n\n## Terminology\n\n\n\n### Entity\n\nSubGraph entities are regular maps that can be uniquely identified by one (and currently only one) of their entries.\n\n\n\n### Lookup ref\n\nA lookup-ref is a 2-element vector representing an entity's identifying key and its associated value.\n\n\n\n### Normalization\n\nNormalization is the process of eliminating redundancy in entities by replacing every the other entities that it references, aka joins, with lookup refs.\n\n\n\n### Database\n\nThe SubGraph database is a regular map associating lookup refs to their corresponding entities.\n\n\n\n## Usage\n\nThis section shows how to initialize a SubGraph database, add, query and update entities using `re-frame` event handlers and subscriptions.\n\nFor more usage examples of interacting directly with the database, refer to the [usage section in the MapGraph README](https://github.com/stuartsierra/mapgraph#usage)\n\nIn the following examples we'll setup a reactive database dealing with users and their favorite colors. When denormalized the data looks like this:\n\n```clojure\n  {:user/id             1\n   :user/name           \"Pat\"\n   :user/favorite-color {:color/hex  \"9C27B0\"\n                         :color/name \"Purple\"}\n   :user/friends        [{:user/id             2\n                          :user/name           \"Reese\"\n                          :user/favorite-color {:color/hex  \"D50000\"\n                                                :color/name \"Red\"}}]}\n```\n\n\nTo get started we'll need re-frame, subgraph and the interop namespaces.\n\n```clojure\n(ns example\n  (:require\n   [re-frame.core :as re-frame]\n   [vimsical.subgraph :as sg]\n   [vimsical.subgraph.re-frame :as sg.re-frame]))\n```\n\n\n\n### Database initialization\n\nIn order to avoid duplication we want to normalize not only users, but colors as well, looking at our denormalized data we see that our identifying attributes are `:user/id` and `:color/hex`.\n\nWe can create a new empty database with `sg/new-db` but for normalization to work we'll have to configure it by adding our identifying attributes using `add-id-attr`.\n\n```clojure\n(defn new-db\n  []\n  (-\u003e (sg/new-db)\n      (sg/add-id-attr :user/id :color/hex)))\n```\n\nWe're now able to produce a new empty db value, so we register a `re-frame` handler to initialize our app db, and dispatch it right away.\n\n```clojure\n(re-frame/reg-event-db ::new-db (constantly (new-db)))\n```\n\n```clojure\n(re-frame/dispatch [::new-db])\n```\n\n\n\n### Adding entities\n\nWe can populate our database using `add` which accepts a variable number of entities. In order to be able to `add` data in reaction to user input in a component we'll need to invoke that function inside a `re-frame` event handler. The most generic of such handlers simply wraps `add`.\n\n```clojure\n(re-frame/reg-event-db\n ::add\n (fn [db [_ \u0026 entities]]\n   (apply sg/add db entities)))\n```\n\nWe can now dispatch `::add` events to populate our database. Note that nested entities are valid and will normalize recursively according to the attributes added with `add-id-attr`.\n\n```clojure\n(re-frame/dispatch\n [::add\n  {:user/id             1\n   :user/name           \"Pat\"\n   :user/favorite-color {:color/hex  \"9C27B0\"\n                         :color/name \"Purple\"}}\n  {:user/id             2\n   :user/name           \"Reese\"\n   :user/favorite-color {:color/hex  \"D50000\"\n                         :color/name \"Red\"}}])\n```\n\n\n\n### Subscriptions and pull queries\n\nOne of the design goals of SubGraph was to enable fully reactive pull queries against a (r)atom. Reactive pull queries return reactions that not only update with changes in that entity's pattern, but recursively for any joined entity.\n\n`vimsical.subgraph.re-frame/pull` is an api-compatible version of `vimsical.subgraph/pull` designed to work with (r)atoms. For convenience we also provide a re-frame raw subscription handler `vimsical.subgraph.re-frame/raw-sub-handler`.\n\nWe register a generic subscription handler that we'll call `:q`.\n\n```clojure\n(re-frame/reg-sub-raw :q sg.re-frame/raw-sub-handler)\n```\n\nThis subscription accepts any pattern and lookup-ref, and will return a fully reactive reaction graph. \n\n```clojure\n(deref\n (re-frame/subscribe\n  [:q \n   ;; Pattern\n   [:user/name {:user/favorite-color [:color/name]}]\n   ;; Lookup ref\n   [:user/id 2]]))\n\n;; =\u003e #:user{:name \"Reese\", :favorite-color #:color{:name \"Red\"}}\n```\n\n\n\n### Updates\n\nTo update an entity we simply `add` it again to the database, the semantics are equivalent to that of `merge`. \n\nLet's create a cycle by updating Pat and Reese, making them friends by referencing each other.\n\n```clojure\n(re-frame/dispatch\n [::add\n  {:user/id 1 :user/friends #{{:user/id 2}}}\n  {:user/id 2 :user/friends #{{:user/id 1}}}])\n```\n\nSince queries can be arbitrarily nested, we can ask for Pat's friends' favorite colors.\n\n```clojure\n(deref\n (re-frame/subscribe\n  [:q\n   [:user/name\n    {:user/friends\n     [:user/name {:user/favorite-color [:color/name]}]}]\n   [:user/id 1]]))\n\n;; =\u003e #:user{:name \"Pat\", :friends #{#:user{:name \"Reese\", :favorite-color #:color{:name \"Red\"}}}}\n```\n\nAnd then update Reese's favorite color.\n\n```clojure\n(re-frame/dispatch\n [::add\n  {:user/id             2\n   :user/favorite-color {:color/hex  \"1789d6\"\n                         :color/name \"DodgerBlue3\"}}])\n```\n\nThanks to normalization and our reactive graph, our subscription for Pat's friends' favorite colors updates, showing Reese's new favorite color.\n\n```clojure\n(deref\n (re-frame/subscribe\n  [:q\n   [:user/name\n    {:user/friends\n     [:user/name {:user/favorite-color [:color/name]}]}]\n   [:user/id 1]]))\n\n;; =\u003e #:user{:name \"Pat\", :friends #{#:user{:name \"Reese\", :favorite-color #:color{:name \"DodgerBlue3\"}}}}\n```\n\n\n\n## Comparison with MapGraph\n\nSubGraph builds on a fork of Stuart Sierra's [MapGraph](https://github.com/stuartsierra/mapgraph), the implementation diverged in order to add support for (r)atoms in the `pull` api.\n\nThe `vimsical.subgraph` namespace is api-compatible with `com.stuartsierra.mapgraph`, however SubGraph extends the pull query syntax with support for:\n\n- Recursive join queries\n\nUnbounded recursion should be used with caution since there is no mechanism to detect cycles. In our example of mutual friends, the following query would run infinitely .\n\n```clojure\n[:user/name\n {:user/favorite-color [:color/name]}\n {:user/friends '...}]\n```\n\nA number can be provided to limit the level of nesting.\n\n```clojure\n[:user/name\n {:user/favorite-color [:color/name]}\n {:user/friends 3}]\n```\n\n\n\n- Link references\n\nApplications commonly need to keep track of global references, such as the current user or a selection. This is easily achieved by storing a lookup-ref as a value in the database, for example `{:app/user [:user/id 1]}`. \n\nSubGraph supports this pattern with a special query syntax identical to that of `om.next`.\n\n```clojure\n[{[:app/user '_] \n  [:user/name\n    {:user/favorite-color [:color/name]}\n    {:user/friends 3}]}]\n```\n\n\n\n## Comparison with om.next \n\n\n### Limitations\n\n- SubGraph currently doesn't support union queries\n- SubGraph's query parser is not extensible, as such there is no support for parametrized joins and mutations\n\n\n### Differences\n\n- SubGraph doesn't have an indexer and relies on reagent reactions to update components when data changes.\n- Normalization is driven by the database's `id-attrs`, when adding entities no query, or component tree, is required.\n\n\n\n## Comparison with Datomic/Datascript\n\nRefer to the comparison in the [MapGraph Repo](https://github.com/stuartsierra/mapgraph#comparison-with-datomicdatascript)\n\n\n\n## Bug reports\n\nPlease file issues on GitHub with minimal sample code that demonstrates the problem.\n\n\n\n## Contributing\n\nPull requests are welcome!\n\n\n\n## Special thanks to \n\n[Stuart Sierra](https://github.com/stuartsierra/mapgraph) for MapGraph\n\n[David Nolen](https://github.com/swannodette) for ClojureScript and om.next\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fden1k%2Fsubgraph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fden1k%2Fsubgraph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fden1k%2Fsubgraph/lists"}