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' that goes in or out of a Hyperion datastore is a map.
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.
```clojure
; 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 dogs 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 up to 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
```
### Deleting a value:
```clojure
; if you have a key...
(delete-by-key my-key)
; otherwise
(delete-by-kind :dog) ; deletes all records with :kind of "dog"
(delete-by-kind :dog :filters [:= :name "Fido"]) ; deletes all dogs whos name is Fido
(delete-by-kind :dog :filters [[:> :age 2][:< :age 5]]) ; deletes all dogs between the age of 2 and 5 (exclusive)
```
The `:filter` and `:sort` options are usable in `find-by-kind`, `find-by-all-kinds`, and `delete-by-kind`. The `:limit` option may also be used in the `find-by-` functions.
Note: Filters and Sorts on `:key` are not supported. Some datastore implementations don't store the `:key` along with the data, so you can't very well filter or sort something that aint there.
### Entities
Used to define entities. An entity is simply an encapsulation 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 may be 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.
Example:
```clojure
(use 'hyperion.types)
(defentity Citizen
[name]
[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 (foreign-key :citizen)] ; :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
)
In a traditional SQL database, you may have a schema that looks like this:
users:
* id
* first_name
* created_at
* updated_at
profiles:
* id
* user_id
* created_at
* updated_at
Since Hyperion presents every underlying datastore as a key-value store, configuring Hyperion to use this schema is a little tricky, but certainly possible.
This is what the coresponding `defentity` notation would be:
Using the `foreign-key` type, our foreign key references are stored following the conventions of the underlying datastore. In this example, the `user-key` field will be packed as an integer id, as stored in the `user-id` column.
If your schema requires foreign keys, **ALWAYS USE THE FOREIGN KEY TYPE**. If you do not, you will be storing generated keys instead of actual database ids. **DO NOT DO THIS**. If Hyperion changes the way it generates keys, all of your foreign key data will be useless.
#### Types
All hyperion implementations provide built-in support for the following types:
Implementations may either support the type Natively or with a packer/unpacker. If they are natively supported, no configuration is needed. If supported by a packer/unpacker, you must explicitly configure the type. For example:
It is always best to explicitly state the types of all fields, regardless of implementation, so that you don't have to worry about the differences between datastores.
#### Unsupported Types
The following types do not have built-in support:
* `java.math.BigInteger`
* `java.math.BigDecimal`
There are many different opinions on the best way to store these types. We will leave it up to you to store them in the way that you see fit.
## Logging
Many of the Hyperion components will log informative information (more logging has yet to be added). The default log level is _Info_.
Not much is logged at the info level. To get more informative log message, turn on the _Debug_ log level.
```clojure
(hyperion.log/debug!)
```
You can also log your own messages.
```clojure
(hyperion.log/debug "This is a debug message")
(hyperion.log/info "Hey, here's some info!")
```
The complete list of log levels (which come from [timbre](https://github.com/ptaoussanis/timbre)) are `[:trace :debug :info :warn :error :fatal :report]`.
## Full API
To learn more, download hyperion.api and load up the REPL.
user=> (doc delete-by-key)
-------------------------
hyperion.api/delete-by-key
([key])
Removes the record stored with the given key.
Returns nil no matter what.
```