Clojure API for generic persistence.
Clojure Ruby
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

Hyperion Build Status

1 API, multiple database backends.

Hyperion provides you with a simple API for data persistence allowing you to delay the choice of database without delaying your development.

There are a few guiding principles for Hyperion.

  1. key/value store. All Hyperion implementations, even for relational databases, conform to the simple key/value store API.
  2. values are maps. Every 'value' the goes in or out of a Hyperion datastore is map, or a defrecord when using defentity.
  3. :key and :kind. Every 'value' must have a :kind entry; a short string like "user" or "product". Persisted 'value's will have a :key entry; strings generated by the datastore.
  4. Search with data. All searches are described by data. See find-by-kind below.

Hyperion Implementations:



:dependencies [[hyperion/hyperion-<impl here> "3.1.0"]]


Creating a datastore

hyperion.core provide a convenient factory function for instantiating any datastore implementation

(use 'hyperion.core)
(new-datastore :implementation :memory)
(new-datastore :implementation :mysql :connection-url "jdbc:mysql://localhost:3306/myapp?user=root" :database "myapp")
(new-datastore :implementation :mongo :host "localhost" :port 27017 :database "myapp" :username "test" :password "test")

Each implementation provides their own facilities our course:

(use 'hyperion.mongo)
(new-mongo-datastore :host "localhost" :port 27017 :database "myapp" :username "test" :password "test")
(let [mongo (open-mongo :host "" :port 27017)
      db (open-database mongo "myapp" :username "test" :password "test")]
   (new-mongo-datastore db))

Installing a datastore

; with brute force
(set-ds! (new-datastore ...))
; with elegance
(binding [*ds* (new-datastore ...)]
  ; persistence stuff here)

If you can bind the datastore once at high level in your application that's ideal. Otherwise use the brute force set-ds! technique.

Saving a value:

(save {:kind :foo})
;=> {:kind "foo" :key "generated key"}
(save {:kind :foo} {:value :bar})
;=> {:kind "foo" :value :bar :key "generated key"}
(save {:kind :foo} :value :bar)
;=> {:kind "foo" :value :bar :key "generated key"}
(save {:kind :foo} {:value :bar} :another :fizz)
;=> {:kind "foo" :value :bar :another :fizz :key "generated key"}
(save (citizen) :name "Joe" :age 21 :country "France")
;=> #<Citizen {:kind "citizen" :name "Joe" :age 21 :country "France" ...}>

Loading a value

; if you have a key...
(find-by-key my-key)

; otherwise
(find-by-kind :dog) ; returns all records with :kind of "dog"
(find-by-kind :dog :filters [:= :name "Fido"]) ; returns all dogs whos name is Fido
(find-by-kind :dog :filters [[:> :age 2][:< :age 5]]) ; returns all dogs between the age of 2 and 5 (exclusive)
(find-by-kind :dog :sorts [:name :asc]) ; returns all dogs in alphebetical order of their name
(find-by-kind :dog :sorts [[:age :desc][:name :asc]]) ; returns all dogs ordered from oldest to youngest, and gos of the same age ordered by name
(find-by-kind :dog :limit 10) ; returns upto 10 dogs in undefined order
(find-by-kind :dog :sorts [:name :asc] :limit 10) ; returns upto the first 10 dogs in alphebetical order of their name
(find-by-kind :dog :sorts [:name :asc] :limit 10 :offset 10) ; returns the second set of 10 dogs in alphebetical order of their name

Filter operations and acceptable syntax:

  • := "=" "eq"
  • :< "<" "lt"
  • :<= "<=" "lte"
  • :> ">" "gt"
  • :>= ">=" "gte"
  • :!= "!=" "not"
  • :contains? "contains?" :contains "contains" :in? "in?" :in "in"

Sort orders and acceptable syntax:

  • :asc "asc" :ascending "ascending"
  • :desc "desc" :descending "descending"


Used to define entities. An entity is simply an encapulation of data that is persisted. The advantage of using entities are:

  • they limit the fields persisted to only what is specified in their definition.
  • default values can be assigned to fields
  • types, packers, and unpackers can be assigned to fields. Packers allow you to manipulate a field (perhaps serialize it) before it is persisted. Unpacker conversly manipulate fields when loaded. Packers and unpackers maybe a fn (which will be excuted) or an object used to pivot the pack and unpack multimethods. A type (object) is simply a combined packer and unpacker.
  • constructors are provided
  • they are represented by records (defrecord) instead of plain maps. This allows you to use extend-type on them if you choose.


(defentity Citizen
    [age :packer ->int] ; ->int is a function defined in your code.
    [gender :unpacker ->string] ; ->string is a customer function too.
    [occupation :type my.ns.Occupation] ; and then we define pack/unpack for my.ns.Occupation
    [spouse-key :type :key] ; :key is a special type that pack string keys into implementation-specific keys
    [country :default "USA"] ; newly created records will use the default if no value is provided
    [created-at] ; populated automaticaly
    [updated-at] ; also populated automatically

(save (citizen :name "John" :age "21" :gender :male :occupation coder :spouse-key "abc123"))

;=> #<Citizen {:kind "citizen" :key "some generated key" :country "USA" :created-at #<java.util.Date just-now> :updated-at #<java.util.Date just-now> ...)

Full API

To learn more, downlaod hyperion.core and load up the REPL.

user=> (keys (ns-publics 'hyperion.core))
(delete-by-key save* ds-count-by-kind count-all-kinds AsField ds-all-kinds ds-delete-by-kind save
find-by-key reload pack create-entity-with-defaults ds-pack-key ds-unpack-key delete-by-kind defentity
*ds* ds-save ->kind before-save find-by-kind count-by-kind after-load after-create ds-delete-by-key
new-datastore spec-for ds-find-by-kind ds Specable unpack ds-find-by-key Datastore create-entity
->field AsKind set-ds! *entity-specs* find-all-kinds new?)
user=> (doc delete-by-key)
  Removes the record stored with the given key.
  Returns nil no matter what.