{"id":36029340,"url":"https://github.com/8thlight/hyperion","last_synced_at":"2026-04-04T08:08:55.079Z","repository":{"id":2622028,"uuid":"3606708","full_name":"8thlight/hyperion","owner":"8thlight","description":"Clojure API for generic persistence.","archived":false,"fork":false,"pushed_at":"2017-11-20T09:45:18.000Z","size":1083,"stargazers_count":118,"open_issues_count":3,"forks_count":18,"subscribers_count":100,"default_branch":"master","last_synced_at":"2023-11-07T20:52:14.656Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/8thlight.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-03-02T22:22:49.000Z","updated_at":"2023-07-25T13:44:24.000Z","dependencies_parsed_at":"2022-08-29T01:51:04.316Z","dependency_job_id":null,"html_url":"https://github.com/8thlight/hyperion","commit_stats":null,"previous_names":[],"tags_count":15,"template":null,"template_full_name":null,"purl":"pkg:github/8thlight/hyperion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8thlight%2Fhyperion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8thlight%2Fhyperion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8thlight%2Fhyperion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8thlight%2Fhyperion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/8thlight","download_url":"https://codeload.github.com/8thlight/hyperion/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8thlight%2Fhyperion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31392217,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2026-01-10T15:05:17.746Z","updated_at":"2026-04-04T08:08:55.070Z","avatar_url":"https://github.com/8thlight.png","language":"Clojure","readme":"\u003cimg src=\"https://raw.github.com/8thlight/hyperion/master/hyperion_logo.png\" alt=\"Hyperion logo\" title=\"Titan of Light\" align=\"right\"/\u003e\n# Hyperion [![Build Status](https://secure.travis-ci.org/8thlight/hyperion.png)](http://travis-ci.org/8thlight/hyperion)\n\n\u003cem\u003e1 API, multiple database backends.\u003c/em\u003e\n\nHyperion provides you with a simple API for data persistence allowing you to delay the choice of database without delaying your development.\n\nThere are a few guiding principles for Hyperion.\n\n 1. key/value store.  All Hyperion implementations, even for relational databases, conform to the simple key/value store API.\n 2. values are maps.  Every 'value' that goes in or out of a Hyperion datastore is a map.\n 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.\n 4. Search with data.  All searches are described by data.  See find-by-kind below.\n\nHyperion Implementations:\n\n * [memory](https://github.com/8thlight/hyperion/blob/master/api/src/hyperion/memory.clj) - an in-memory datastore, ideal for testing, included in hyperion-api package\n * [gae](https://github.com/8thlight/hyperion/tree/master/gae) - [Google App Engine Datastore](https://developers.google.com/appengine/docs/python/datastore/overview)\n * [mongo](https://github.com/8thlight/hyperion/tree/master/mongo) - [Mongo DB](http://www.mongodb.org/)\n * [mysql](https://github.com/8thlight/hyperion/tree/master/mysql) - [MySQL](http://www.mysql.com/)\n * [postgres](https://github.com/8thlight/hyperion/tree/master/postgres) - [PostgreSQL](http://www.postgresql.org/)\n * [riak](https://github.com/8thlight/hyperion/tree/master/riak) - [Riak](http://wiki.basho.com/Riak.html)\n * [sqlite](https://github.com/8thlight/hyperion/tree/master/sqlite) - [SQLite](http://www.sqlite.org/)\n * [redis](https://github.com/8thlight/hyperion/tree/master/redis) - [Redis](http://redis.io/)\n\n## Installation\n\n### Leiningen\n\n```clojure\n:dependencies [[hyperion/hyperion-\u003cimpl here\u003e \"3.7.0\"]]\n```\n\n## Usage\n\n### Creating a datastore\n\nhyperion.api provides a convenient factory function for instantiating any datastore implementation\n\n```clojure\n(use 'hyperion.api)\n(new-datastore :implementation :memory)\n(new-datastore :implementation :mysql :connection-url \"jdbc:mysql://localhost:3306/myapp?user=root\" :database \"myapp\")\n(new-datastore :implementation :mongo :host \"localhost\" :port 27017 :database \"myapp\" :username \"test\" :password \"test\")\n```\n\nEach implementation provides their own facilities of course:\n\n```clojure\n(use 'hyperion.mongo)\n(new-mongo-datastore :host \"localhost\" :port 27017 :database \"myapp\" :username \"test\" :password \"test\")\n;or\n(let [mongo (open-mongo :host \"127.0.0.1\" :port 27017)\n      db (open-database mongo \"myapp\" :username \"test\" :password \"test\")]\n   (new-mongo-datastore db))\n```\n\n### Installing a datastore\n\n```clojure\n; with brute force\n(set-ds! (new-datastore ...))\n; with elegance\n(binding [*ds* (new-datastore ...)]\n  ; persistence stuff here)\n```\n\nIdeally, bind the datastore once at a high level in your application, if you can. Otherwise use the brute force `set-ds!` technique.\n\n### Saving a value:\n\n```clojure\n(save {:kind :foo})\n;=\u003e {:kind \"foo\" :key \"generated key\"}\n(save {:kind :foo} {:value :bar})\n;=\u003e {:kind \"foo\" :value :bar :key \"generated key\"}\n(save {:kind :foo} :value :bar)\n;=\u003e {:kind \"foo\" :value :bar :key \"generated key\"}\n(save {:kind :foo} {:value :bar} :another :fizz)\n;=\u003e {:kind \"foo\" :value :bar :another :fizz :key \"generated key\"}\n(save (citizen) :name \"Joe\" :age 21 :country \"France\")\n;=\u003e #\u003c{:kind \"citizen\" :name \"Joe\" :age 21 :country \"France\" ...}\u003e\n```\n\n### Updating a value:\n\n```clojure\n(let [record (save {:kind :foo :name \"Sue\"})\n      new-record (assoc record :name \"John\")]\n  (save new-record))\n;=\u003e {:kind \"foo\" :name \"John\" :key \"generated key\"}\n```\n\n### Loading a value:\n\n```clojure\n; if you have a key...\n(find-by-key my-key)\n\n; otherwise\n(find-by-kind :dog) ; returns all records with :kind of \"dog\"\n(find-by-kind :dog :filters [:= :name \"Fido\"]) ; returns all dogs whos name is Fido\n(find-by-kind :dog :filters [[:\u003e :age 2][:\u003c :age 5]]) ; returns all dogs between the age of 2 and 5 (exclusive)\n(find-by-kind :dog :sorts [:name :asc]) ; returns all dogs in alphebetical order of their name\n(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\n(find-by-kind :dog :limit 10) ; returns upto 10 dogs in undefined order\n(find-by-kind :dog :sorts [:name :asc] :limit 10) ; returns up to the first 10 dogs in alphebetical order of their name\n(find-by-kind :dog :sorts [:name :asc] :limit 10 :offset 10) ; returns the second set of 10 dogs in alphebetical order of their name\n```\n\n### Deleting a value:\n\n```clojure\n; if you have a key...\n(delete-by-key my-key)\n\n; otherwise\n(delete-by-kind :dog) ; deletes all records with :kind of \"dog\"\n(delete-by-kind :dog :filters [:= :name \"Fido\"]) ; deletes all dogs whos name is Fido\n(delete-by-kind :dog :filters [[:\u003e :age 2][:\u003c :age 5]]) ; deletes all dogs between the age of 2 and 5 (exclusive)\n```\n\n### Filters and Sorts\n\nFilter operations and acceptable syntax:\n\n  * `:=` `\"=\"` `\"eq\"`\n  * `:\u003c` `\"\u003c\"` `\"lt\"`\n  * `:\u003c=` `\"\u003c=\"` `\"lte\"`\n  * `:\u003e` `\"\u003e\"` `\"gt\"`\n  * `:\u003e=` `\"\u003e=\"` `\"gte\"`\n  * `:!=` `\"!=\"` `\"not\"`\n  * `:contains?` `\"contains?\"` `:contains` `\"contains\"` `:in?` `\"in?\"` `:in` `\"in\"`\n\nSort orders and acceptable syntax:\n\n * `:asc` `\"asc\"` `:ascending` `\"ascending\"`\n * `:desc` `\"desc\"` `:descending` `\"descending\"`\n\nThe `: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.\n\n\u003cem\u003eNote:\u003c/em\u003e 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.\n\n### Entities\n\nUsed to define entities. An entity is simply an encapsulation of data that is persisted.\nThe advantage of using entities are:\n\n * they limit the fields persisted to only what is specified in their definition.\n * default values can be assigned to fields.\n * types, packers, and unpackers can be assigned to fields. Packers\n     allow you to manipulate a field (perhaps serialize it) before it\n     is persisted. Unpacker conversly manipulate fields when loaded.\n     Packers and unpackers may be a fn (which will be excuted) or an\n     object used to pivot the pack and unpack multimethods.\n     A type (object) is simply a combined packer and unpacker.\n * constructors are provided.\n\nExample:\n\n```clojure\n(use 'hyperion.types)\n\n(defentity Citizen\n    [name]\n    [age :packer -\u003eint] ; -\u003eint is a function defined in your code.\n    [gender :unpacker -\u003estring] ; -\u003estring is a customer function too.\n    [occupation :type my.ns.Occupation] ; and then we define pack/unpack for my.ns.Occupation\n    [spouse-key :type (foreign-key :citizen)] ; :key is a special type that pack string keys into implementation-specific keys\n    [country :default \"USA\"] ; newly created records will use the default if no value is provided\n    [created-at] ; populated automaticaly\n    [updated-at] ; also populated automatically\n    )\n\n(save (citizen :name \"John\" :age \"21\" :gender :male :occupation coder :spouse-key \"abc123\"))\n\n;=\u003e #\u003c{:kind \"citizen\" :key \"some generated key\" :country \"USA\" :created-at #\u003cjava.util.Date just-now\u003e :updated-at #\u003cjava.util.Date just-now\u003e ...)\n```\n\n#### Foreign Keys\n\nIn a traditional SQL database, you may have a schema that looks like this:\n\nusers:\n  * id\n  * first_name\n  * created_at\n  * updated_at\n\nprofiles:\n  * id\n  * user_id\n  * created_at\n  * updated_at\n\nSince Hyperion presents every underlying datastore as a key-value store, configuring Hyperion to use this schema is a little tricky, but certainly possible.\n\nThis is what the coresponding `defentity` notation would be:\n\n``` clojure\n(use 'hyperion.api)\n(use 'hyperion.types)\n\n(defentity :users\n  [first-name]\n  [created-at]\n  [updated-at]\n  )\n\n(defentity :profiles\n  [user-key :type (foreign-key :users) :db-name :user-id]\n  [created-at]\n  [updated-at]\n  )\n\n(let [myles (save {:kind :users :first-name \"Myles\"})\n      myles-profile (save {:kind :profiles :user-key (:key myles)})]\n; myles =\u003e {:key \"b26316a0248244bab65c699778897ab9\", :created-at #inst \"2012-12-05T15:41:23.589-00:00\", :updated-at #inst \"2012-12-05T15:41:23.589-00:00\", :first-name \"Myles\", :kind \"users\"}\n; myles is stored in the users table as:\n; | id | first_name | created_at | updated_at |\n; | 1  | Myles      | \u003ctime\u003e     | \u003ctime\u003e     |\n\n; myles-profile =\u003e {:key \"7202968b5ecf47aab686990750a3238a\", :user-key \"b26316a0248244bab65c699778897ab9\", :created-at #inst \"2012-12-05T15:43:16.529-00:00\", :updated-at #inst \"2012-12-05T15:43:16.529-00:00\", :kind \"profiles\"}\n; myles' profile is stored in the profiles table as:\n; | id | user_id | created_at | updated_at |\n; | 1  | 1       | \u003ctime\u003e     | \u003ctime\u003e     |\n\n  (= (find-by-key (:user-key myles-profile)) myles) ;=\u003e true\n  )\n\n```\n\nUsing 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.\n\nIf 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.\n\n#### Types\n\nAll hyperion implementations provide built-in support for the following types:\n\n* `java.lang.Boolean`\n* `java.lang.Byte`\n* `java.lang.Short`\n* `java.lang.Integer`\n* `java.lang.Long`\n* `java.lang.Float`\n* `java.lang.Double`\n* `java.lang.Character`\n* `java.lang.String`\n* `clojure.lang.Keyword`\n\nImplementations 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:\n\n``` clojure\n(defentity :users\n  [first-name :type java.lang.String]\n  [age :type java.lang.Integer])\n```\n\nIt 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.\n\n#### Unsupported Types\n\nThe following types do not have built-in support:\n\n* `java.math.BigInteger`\n* `java.math.BigDecimal`\n\nThere 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.\n\n## Logging\n\nMany of the Hyperion components will log informative information (more logging has yet to be added).  The default log level is _Info_.\nNot much is logged at the info level.  To get more informative log message, turn on the _Debug_ log level.\n\n```clojure\n(hyperion.log/debug!)\n```\n\nYou can also log your own messages.\n\n```clojure\n(hyperion.log/debug \"This is a debug message\")\n(hyperion.log/info \"Hey, here's some info!\")\n```\n\nThe complete list of log levels (which come from [timbre](https://github.com/ptaoussanis/timbre)) are `[:trace :debug :info :warn :error :fatal :report]`.\n\n## Full API\n\nTo learn more, download hyperion.api and load up the REPL.\n\n```clojure\nuser=\u003e (keys (ns-publics 'hyperion.api))\n(delete-by-key save* count-all-kinds save find-by-key reload pack create-entity-with-defaults delete-by-kind defentity *ds*\nbefore-save find-by-kind count-by-kind after-load after-create new-datastore ds unpack create-entity set-ds! find-all-kinds new?)\n\nuser=\u003e (doc delete-by-key)\n-------------------------\nhyperion.api/delete-by-key\n([key])\n  Removes the record stored with the given key.\n  Returns nil no matter what.\n```\n","funding_links":[],"categories":["\u003ca name=\"Clojure\"\u003e\u003c/a\u003eClojure"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F8thlight%2Fhyperion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F8thlight%2Fhyperion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F8thlight%2Fhyperion/lists"}