SkillAgentSearch skills...

Posh

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

Install / Use

/learn @mpdairy/Posh
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Now maintained here: https://github.com/denistakeda/posh

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

View on GitHub
GitHub Stars457
CategoryDevelopment
Updated2mo ago
Forks47

Languages

Clojure

Security Score

95/100

Audited on Jan 9, 2026

No findings