posh

A luxuriously simple and powerful way to make front-ends with DataScript and Reagent in Clojure.

Subscribe to updates I use posh


Statistics on posh

Number of watchers on Github 366
Number of open issues 17
Average time to close an issue about 1 month
Main language Clojure
Average time to merge a PR about 5 hours
Open pull requests 6+
Closed pull requests 0+
Last commit about 1 year ago
Repo Created over 2 years ago
Repo Last Updated 3 months ago
Size 1.79 MB
Organization / Authormpdairy
Contributors2
Page Updated
Do you use posh? Leave a review!
View open issues (17)
View posh activity
View on github
Latest Open Source Launches
Trendy new open source projects in your inbox! View examples

Subscribe to our mailing list

Evaluating posh for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)

Posh

Posh is a ClojureScript / React library that lets you use a single DataScript database to store your app state. Components access the data they need to render by calling DataScript queries with q or pull and are only updated when the query changes. transact! is used within components to change the global state. If you are familiar with Datomic, you will find Posh incredibly easy to use. If not, it's worth learning because of the power and versatility it will give your components.

Posh is now self-contained and can be used with multiple front-ends (see posh.core), such as Reagent, Rum, or Quiescent. Only Reagent is currently well-supported by Posh, and is the focus of this documentation.

posh.reagent uses Reagent and can be integrated with your current Reagent project. Because it uses a single database to store app state, like Om or re-frame, it is fitting to write large, extensible apps and reusable components, with the added benefit of being much simpler to use and having a more expressive data retrieval and state updating syntax.

Posh is also very fast because the in-component data queries only run when the database is updated with relevant data (found by pattern matching on the tx report).

For example, below is a component that displays a list of a person's age, name, and weight. The component will only re-render when something in the database changed an attribute of the person-id entity:

(defn person [conn person-id]
  (let [p @(pull conn '[*] person-id)]
    [:ul
     [:li (:person/name p)]
     [:li (:person/age p)]
     [:li (:person/weight p)]]))

Resources

Posh chat room on Gitter: https://gitter.im/mpdairy/posh

I am also currently looking for contract work or employment on a project that uses Posh.

Examples:

Posh Todo List - A todo list with categories, edit boxes, checkboxes, and multi-stage delete buttons (trashy live demo).

Projects Using Posh:

  • Zetawar A highly customizable, open source, turn-based, tactical strategy web game written in ClojureScript.
  • Datsys A web framework.

Usage

Start a Reagent project and include these dependencies:

[posh "0.5.5"]

Require in Reagent app files:

(ns example
  (:require [reagent.core :as r]
            [posh.reagent :refer [pull q posh!]]
            [datascript.core :as d]))

Important changes

0.5.4

  • added option fields to q and pull. Currently the only option is :cache :forever, which will keep the query results caches forever, even after the component using that query is un-rendered. (Thanks, metasoarous)

0.5.3

  • added filter-tx, filter-q, and filter-pull to posh.reagent

0.5.1

  • get-else now works with q, but still no pull in q.
  • q with no :in args now works properly

0.5

  • You must require posh.reagent in your ns's instead of posh.core. This is because Posh 0.5's core is now front-end agnostic and Reagent is just one of the front-ends it will work with (Rum and Quiescent soon to come!)
  • Previously, Posh's q took the conn as the first argument. Now, the conn is placed behind the query, in the args, as in DataScript or Datomic's q.
  • db-tx, pull-tx, and q-tx are now deprecated. The tx-patterns generated for pull are exact and for q are pretty thorough.
  • q with get-else and pull do not currently work in 0.5, though they sort-of worked in the older version. If you need to use those, just keep using the older version until those expressions are supported.

Overview

Posh gives you two functions to retrieve data from the database from within Reagent components: pull and q. They watch the database's transaction report and only update (re-render) the hosting component when one of the transacted datoms affects the requested data.

posh!

(posh! [DataScript conn1] ...)

Sets up the tx-report listener for a conn.

(def conn (d/create-conn))

(posh! conn)

New in Posh 0.5, you can posh! multiple conns together if you intend to ever use them together in a q query:

(posh! conn users-conn styles-conn)

pull

(pull [conn] [pull pattern] [entity id])

pull retrieves the data specified in pull-pattern for the entity with entity-id. pull can be called from within any Reagent component and will re-render the component only when the pulled information has changed.

Posh's pull operates just like Datomic / Datascript's pull except it takes a conn instead of a db. (See Datomic's pull)

Posh's pull only attempts to pull any new data if there has been a transaction of any datoms that have changed the data it is looking at. For example:

(pull conn '[:person/name :person/age] 1234)

Would only do a pull into Datascript if there has been a transaction changing :person/name or :person/age for entity 1234.

Below is an example that pulls all of the info from the entity with id whenever id is updated and increases its age whenever clicked:

(defn pull-person [id]
  (let [p @(pull conn '[*] id)]
    (println "Person: " (:person/name p))
    [:div
     {:on-click #(transact! conn [[:db/add id :person/age (inc (:person/age p))]])}
     (:person/name p) ": " (:person/age p)]))

q

(q [query] & args)

q queries for data from the database according to the datalog rules specified in the query. It must be called within a Reagent component and will only update the component whenever the data it is querying has changed. See Datomic's Queries and Rules for how to do datalog queries. args are extra variables, including the conn or conns from which you will be querying, that DataScript's q looks for after the [:find ...] query if the query has an :in specification. Note that Posh's q takes conns rather than dbs.

Whenever the database has changed, q will check the transacted datoms to see if anything relevant to its query has occured. If so, q runs Datascript's q and compares the new query to the old. If it is different, the hosting component will update with the new data.

Below is an example of a component that shows a list of people's names who are younger than a certain age. It only attempts to re-query when someone's age changes or a young person's name changes:

(q '[:find [?name ...]
     :in $ ?old
     :where
     [?p :person/age ?age]
     [(< ?age ?old)]
     [?p :person/name ?name]]
   conn
   old-age)

Currently, pull is not supported inside q. It is recommended to query for the eids, manually send them to components with a separate pull for each eid.

Filters

Filters allow you to select a subset of the database to be accessed by queries. Filters can be faster because TX datoms must first pass through a filter before passing on to any queries that use that filter. However, the filters currently just use Datascript's filter function and lazily check each queried datom with a pattern matching predicate to see if it passes the filter, so in reality filters might just slow you down. In the future there will be an option to cache the filtered db, which should improve speed of reliant queries.

Filters return a value that can be passed in to queries or other filters in place of the root conn. They should not be dereffed.

filter-tx

filter-tx takes a poshdb or conn and a list of tx-patterns. The resulting filtered db consists only of datoms that satisfy one of those patterns.

The following filter would make a db of only task and category names.

(defn test-filter-tx [conn]
  (let [filter0 (p/filter-tx conn '[[_ :task/name] [_ :category/name])]
    [:div
     [:p "filter-tx: "(pr-str filter0) (rand-int 999999)]
     (pr-str @(p/q '[:find ?v
                     :where
                     [_ _ ?v]]
                   filter0))]))

The q would return a list of all the task and category names. Because filter datom evaluation is currently lazy, the q query would have to check every single entity in the database to see if it passes the filter, and is thus not very efficient.

filter-pull

filter-pull creates a filtered db consisting of everything touched by the pull query. For example:

(p/filter-pull conn '[{:task/_category [:task/name]}]
               [:category/name "Hobby"])

This would return a filtered db that consists of the name of every task belonging to the Hobby category.

filter-q

filter-q queries for entity id's and creates a filtered db consisting of those entities and all their attributes. Although q and filter-q can query from multiple db's/filters, the first argument after the [:find ... :where...] query is assumed to be the parent db.

(p/filter-q '[:find ?task ?cat
              :in $ ?todo
              :where
              [?cat :category/todo ?todo]
              [?task :task/category ?cat]]
            conn
            [:todo/name "Matt's List"])

The above would make a filtered db of all the category and task entities belonging to the todo list named Matt's List.

Combining filters

You can call filters on filters:

(def hobby-tasks (p/filter-pull conn '[{:task/_category [:task/name]}]
                                [:category/name "Hobby"]))
(def hobby-task-names (p/filter-tx hobby-tasks '[[_ :task/name]]))

And soon-to-come you'll be able to use filter-merge on multiple filters to or them together.

transact!

posh.reagent's transact! takes a conn or a posh filter and transacts to the conn or the root conn of the filter.

(transact! conn [[:db/add 123 :person/name "Jim"]])

posh-atom

(get-posh-atom conn)

The cache of all the queries is stored inside the posh-atom, which is pointed to by the conn. If you want to see or edit things under the hood, this is where to go. The dereffed posh-atom can be used as the posh-tree in the functions in posh.core. A wiki will one day explain further.

Advanced Examples

Editable Label

This component will show the text value for any entity and attrib combo. There is an edit button that, when clicked, creates an :edit entity that keeps track of the temporary text typed in the edit box. The done button resets the original value of the entity and attrib and deletes the :edit entity. The cancel button just deletes the :edit entity.

The state is stored entirely in the database for this solution, so if you were to save the db during the middle of an edit, if you restored it later, you would be in the middle of the edit still.

(defn edit-box [conn edit-id id attr]
  (let [edit @(p/pull conn [:edit/val] edit-id)]
    [:span
     [:input
      {:type "text"
       :value (:edit/val edit)
       :onChange #(p/transact! conn [[:db/add edit-id :edit/val (-> % .-target .-value)]])}]
     [:button
      {:onClick #(p/transact! conn [[:db/add id attr (:edit/val edit)]
                                    [:db.fn/retractEntity edit-id]])}
      "Done"]
     [:button
      {:onClick #(p/transact! conn [[:db.fn/retractEntity edit-id]])}
      "Cancel"]]))

(defn editable-label [conn id attr]
  (let [val  (attr @(p/pull conn [attr] id))
        edit @(p/q '[:find ?edit .
                          :in $ ?id ?attr
                          :where
                          [?edit :edit/id ?id]
                          [?edit :edit/attr ?attr]]
                   conn id attr)]
    (if-not edit
      [:span val
       [:button
        {:onClick #(new-entity! conn {:edit/id id :edit/val val :edit/attr attr})}
        "Edit"]]
      [edit-box conn edit id attr])))

This can be called with any entity and its text attrib, like [editable-label conn 123 :person/name] or [editable-label conn 432 :category/title].

Back-end

As of version 0.5, posh.core should be able to run on Datomic databases and keep track of all queries. It can also generate, for any q or pull, the necessary datoms needed in a bare database to get the same result for that q or pull, which means that the front-end can send its graph of queries to the backend and get back any datoms needed to update its db whenever anything relevant changes.

Datsync is a utility that eventually will do this, though currently it just copies the entire Datomic db over to DataScript.

See our Gitter room for updates: https://gitter.im/mpdairy/posh

License

Copyright 2015 Matt Parker

If somebody needs to BSD then sure, it's under that too. Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

posh open issues Ask a question     (View All Issues)
  • almost 2 years spec for datomic/datascript
  • almost 2 years pull-analyze doesn't handle "default"
  • over 2 years Integration with re-frame and subscriber abstraction
  • over 2 years Communicating with back end?
posh open pull requests (View All Pulls)
  • Add a Gitter chat badge to README.md
  • initial js API
  • Add `:cache :forever` option
  • Posh with Datomic (work in progress for now)
  • Fix README typo
  • Client-server syncing (à la Web after Tomorrow / Datsync) (WIP)
posh questions on Stackoverflow (View All Questions)
  • Couldn't open Git Shell in Windows 10! How to open Posh-Git?
  • How to cache credentials for VS online in posh-git?
  • Is `command -v` option required in a POSIX shell? Is posh compliant with POSIX?
  • "git unable to read current working directory no error" posh git (windows 10)
  • POSH: Export Listview to CSV
  • How to use posh-git that comes with GitHub for Windows from custom shell?
  • How do I set the active path for a DFS mount on the client using WMI/PoSH/CMD?
  • PoSh stop working after the installation of SQL Server 2014
  • Posh Git Won't Show Status or Colors on Prompt
  • How to update posh-git
  • Problems to return data advertising in scan api Bluetooth low energy android Posh HD Titan device Android
  • Assembly resolution for PoSh cmdlet causes other module imports to fail
  • How can I check for repo status using posh-git in a PowerShell function?
  • Posh: join DataTables 15000 records
  • Expand Hash variable while reading file content in PoSH 4.0
  • posh-git shows long branch-name
  • Changing "git status" output colors in Posh-Git
  • posh-git colors not set for some local repos
  • Convert BodyStream in MSMQ to text/XML via PoSH?
  • How can I get Posh-Git to launch using 'External Tools' in Visual Studio 2013?
  • POSH: New-Event on Variable changed or filled
  • How to add powershell autocomplete like POSH Git
  • Setting WindowTitle of PowerShell posh-git - escape hyphen?
  • Is there a posh-git equivalent for the Mac?
  • disable Excel Compatibility Checker when saving/closing .xls file via PoSh
  • Posh-hg equivalent for cygwin?
  • How to format output in Posh Server (Powershell web server)?
  • Steps to Install Git from source, install Post-Git from source, and then load up Posh in PowerShell?
  • PoSH - display ultra-wide table in Powershell ISE?
  • Colors of fonts in package manager console with posh-git?
posh list of languages used
Other projects in Clojure