{"id":13801319,"url":"https://github.com/eneroth/plato","last_synced_at":"2025-05-13T11:31:03.480Z","repository":{"id":13941743,"uuid":"16641546","full_name":"eneroth/plato","owner":"eneroth","description":"Incrementally persists atom state to Local Storage in ClojureScript","archived":false,"fork":false,"pushed_at":"2014-12-11T14:14:14.000Z","size":665,"stargazers_count":30,"open_issues_count":1,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-03T09:50:42.707Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eneroth.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}},"created_at":"2014-02-08T11:18:19.000Z","updated_at":"2024-06-04T16:03:16.000Z","dependencies_parsed_at":"2022-09-17T06:42:36.192Z","dependency_job_id":null,"html_url":"https://github.com/eneroth/plato","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/eneroth%2Fplato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eneroth%2Fplato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eneroth%2Fplato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eneroth%2Fplato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eneroth","download_url":"https://codeload.github.com/eneroth/plato/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253932803,"owners_count":21986448,"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":[],"created_at":"2024-08-04T00:01:21.567Z","updated_at":"2025-05-13T11:31:01.832Z","avatar_url":"https://github.com/eneroth.png","language":"Clojure","funding_links":[],"categories":["Awesome ClojureScript"],"sub_categories":["State Management"],"readme":"# Plato\n\n\u003e Our object in the construction of the state is the greatest happiness of the whole, and not that of any one class. \n\u003e \n\u003e *Plato*\n\nThis is why we should try to keep the state separate from the rest of the code.\n\n\n## Purpose\nThe purpose of Plato is to store and restore data to (and from) Local Storage. There are also a couple of quite useful functions for persisting state held within atoms.\n\nThis library grew out of the need to keep storage of application state separate from the application logic. I noticed that I interspersed my application logic with a lot of storage related function calls. Storage and persistence is arguably orthogonal to application logic, and therefore those functions were extracted and put in a separate library.\n\nPlato parses arbitrarily nested hash-maps in atoms and constructs local storage keys from their relative paths.\n\nFor example, given the ```base-key``` (base-keys are explained below) \"myproject\", the hash-map ```{:a 1 :b {:c 2 :d 3}``` is stored in local storage as:\n```\n  Key: \"myproject:a\",   value: \"1\"\n  Key: \"myproject:b:c\", value: \"2\"\n  Key: \"myproject:b:d\", value: \"3\"\n```\n\nDue to limitations in the local storage API, everything except hash-maps is stored as strings in the value field, including vectors and so on. Hash-maps are stored in the formatting of the keys, as shown above.\n\n## Usage\nAdd the following to your project.clj dependencies:\n```clojure\n[plato \"0.1.12\"]\n```\n\nAlmost all Plato functions take a ```base-key``` as the first argument. The reason for this is to make sure that there are no collisions between keys stored in local storage. Make sure you use a different base-key for every atom you intend to persist.\n\nIf you're only using a single atom, and wish to omit the base-key, you can use ```partial```, like this:\n\n```clojure\n(ns myproject.core\n  (:require [plato.core :as plato]))\n\n(def base-key \"myproject\")\n\n(def store-atom! (partial plato/store-atom! base-key))\n```\n\n### Storing and restoring state\nTwo of the main functions in Plato are ```keep-updated!``` and ```restore-atom!```. They are used to store and restore the state of an atom, respectively. There are also a number of functions that can be used to store data more manually. In particular, you *may* need to run ```store-atom!``` once before running ```keep-updated!```, if you have state in the atom that is not currently in Local Storage. This is due to the fact that ```keep-updated!``` works incrementally, and only will persist the *changes* made to the atom.\n\n\n#### Storing state\n\n```keep-updated!``` adds a watch to an atom. This watch reacts to changes in the atom and updates Local Storage accordingly. Only those parts that are changed will be updated. The size of the state held in the atom is irrelevant, only the parts that are changed will be written to Local Storage. If large parts of the state held in the atom is changed, then a larger number of writes to Local Storage will take place.\n\n```clojure\n(plato/keep-updated! base-key an-atom)\n```\n\nIf you wish to see when and what it is being written to local storage, you can supply a third argument (being a boolean) to ```keep-updated!``` in order to turn on console logging. Use it thusly:\n\n```clojure\n(def my-atom (atom {:coords {:x 0\n                             :y 0}}))\n\n(plato/keep-updated! \"myproject\" my-atom true)\n```\n\n```keep-updated!``` will only write to local storage once the atom changes, so if you want to store everything currently in the atom, you have to use ```store-atom!```. Something like this would do,\n\n```clojure\n(def my-atom (atom {:coords {:x 0\n                             :y 0}}))\n                             \n(def my-base-key \"myproject\")                       \n\n(plato/store-atom! my-base-key my-atom)\n\n(plato/keep-updated! my-base-key my-atom true)\n```\n\n\n#### Restoring state\n```restore-atom!``` resets the atom to the state stored in Local Storage, given that there is any.\n\n```clojure\n(plato/restore-atom! base-key an-atom)\n```\n\nFor example,\n\n```clojure\n(def my-atom (atom {:coords {:x 0\n                              :y 0}}))\n\n(plato/restore-atom! \"myproject\" my-atom) ;; Will overwrite current atom content\n```\n\n## Full function list\n\n### Storing\n\n**store!**\n```clojure\n(store! base-key path-vector value)\n```\nUpdates a particular key stored in local storage. For example, \n\n```clojure \n(store! \"com.example.my-atom\" [:foo :bar] \"Hello World!\")\n``` \n\nwill update the key ```\"com.example.my-atom:foo:bar\"``` to have value ```\"Hello World!\"``` in local storage.\n\n**store-many!**\n```clojure\n(store-many! base-key path-vectors)\n```\n\nStores a collection of path vectors in local storage. The path vectors should be on format:\n\n```clojure\n[[:a :b :c] 1]\n```\n  \nFor example,\n\n```clojure\n(def path-vectors [[[:a] 1]\n                   [[:b :c] 2]\n                   [[:b :d] 3]])\n\n(store-many! \"com.example.my-atom\" path-vectors)\n```\n\n**store-state!**\n```clojure\n(store-state! base-key state)\n```\nTakes a hash-map and stores it in local storage. For example,\n\n```clojure\n(store-state! \"com.example.my-state\" {:a {:b {:c 1}}})\n```\n\n**store-atom!**\n```clojure\n(store-atom! base-key an-atom)\n```\nStores the contents of an atom (typically a hash-map) in local storage. For example,\n\n```clojure\n(store-atom! \"com.example.my-atom\" my-atom)\n```\n\n### Retrieving and restoring\n\n**restore**\n```clojure\n(restore base-key path-vector)\n```\n\nGet the value associated with the specified base-key from local storage. For example,\n\n```clojure\n(restore \"com.example.my-atom\" [:a :b :c])\n```\n\n**restore-state**\n```clojure\n(restore-state base-key)\n```\n\nGet all localStorage entries beginning with the given base-key. For example,\n\n```clojure\n(restore-state \"com.example.my-state\")\n```\n\n\n**restore-atom!**\n```clojure\n(restore-atom! base-key an-atom)\n```\n\nGet stored state from local storage and reset the given atom with it. For example,\n\n```clojure\n(restore-atom! \"com.example.my-atom\" my-atom)\n```\n\n### Erasing\n**erase!**\n```clojure\n(erase! base-key path-vector)\n```\n\nRemove a key and corresponding value from local storage. For example,\n\n```clojure\n(erase! \"com.example.my-state\" [:a :b :c])\n```\n\n**erase-many!**\n```clojure\n(erase-many! base-key path-vectors)\n```\nRemove all entries as specified by path vectors, belonging to the given base-key, from local storage. For example,\n```clojure\n(erase-many! \"com.example.my-atom\" [[:a :b :c]\n                                     [:d :e]])\n```\n\n**erase-all!**\n```clojure\n(erase-all! base-key)\n```\nRemove all keys belonging to the given base-key from local storage. For example,\n```clojure\n(erase-all! \"com.example.my-atom\")\n```\n\n### Maintaining state in sync\n**keep-updated!**\n```clojure\n(keep-updated! base-key an-atom log-update)\n```\n\nUpdates local storage with all changes made to an atom. Call with ```true``` as third arg to switch on logging. For example,\n\n```clojure\n(keep-updated! \"com.example.my-atom\" my-atom)\n(keep-updated! \"com.example.my-atom\" my-atom true) ;; Console logging turned on\n```\n\nExample of logging output when logging is turned on,\n```\n…\nUpdating in localStorage [:coords :x] to 561\nUpdating in localStorage [:coords :y] to 174, [:coords :x] to 570\n…\n```\n\n\n### Diffing\n\n**diff-states**\n```clojure\n(diff-states old-state new-state)\n```\nTakes a map representing an old state, and a map representing a new state and returns a vector representing the difference between the two. The first item in the vector details what has been removed and the second what has been added or changed.\n\nAs opposed to [clojure.data/diff](http://clojuredocs.org/clojure_core/clojure.data/diff), this diffing algorithm considers everything that is not a map to be a value. This is in order to prepare the data for Local Storage, which is just a simple string based key-value store.\n\nFor example,\n \n```clojure\n(diff-states {:a 1 \n              :b {:c 2 \n                  :d [1 2 3]} \n              :e \"same\" \n              :f \"removed\"} \n             {:a 2 \n              :b {:c 3 \n                  :d [4 5 6]} \n              :e \"same\"})\n```\n\nOutputs:\n\n```clojure\n[{:f \"removed\"} \n {:a 2\n  :b {:c 3 \n      :d [4 5 6]}}] \n```\n\n\n## License\n\nCopyright © 2014 Henrik Eneroth\n\nDistributed under the Eclipse Public License.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feneroth%2Fplato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feneroth%2Fplato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feneroth%2Fplato/lists"}