{"id":16219899,"url":"https://github.com/kurbatov/norm","last_synced_at":"2025-03-19T11:30:47.273Z","repository":{"id":53267362,"uuid":"284440726","full_name":"kurbatov/norm","owner":"kurbatov","description":"norm is not an ORM (for clojure)","archived":false,"fork":false,"pushed_at":"2024-04-24T11:40:51.000Z","size":141,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-28T18:23:04.736Z","etag":null,"topics":["clojure","database","datamapper","orm","sql"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kurbatov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-02T10:39:23.000Z","updated_at":"2024-04-24T11:40:55.000Z","dependencies_parsed_at":"2024-10-27T20:31:55.356Z","dependency_job_id":"16d5b822-d3a5-446a-9bdb-3cb55e7a4907","html_url":"https://github.com/kurbatov/norm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kurbatov%2Fnorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kurbatov%2Fnorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kurbatov%2Fnorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kurbatov%2Fnorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kurbatov","download_url":"https://codeload.github.com/kurbatov/norm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243985819,"owners_count":20379211,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["clojure","database","datamapper","orm","sql"],"created_at":"2024-10-10T11:56:25.453Z","updated_at":"2025-03-19T11:30:46.944Z","avatar_url":"https://github.com/kurbatov.png","language":"Clojure","readme":"# norm is not an ORM\n\nA Clojure library designed to construct SQL (and maybe other types of)\nqueries dynamically and fetch persistent entities from a storage.\n\nHere *entities* are the units of long-living state in a domain model of an application: person, user,\ncustomer, order, etc. A simplest entity *instance* may be thought of as a row in a table or a node in a graph.\n\nThere are no macros in **norm** - just data structures and functions.\n\n## Capabilities\n\n+ describe entities and their relations as a map\n+ auto-discovery of entities and relations in a database\n+ CRUD for the entities\n+ controlled eager fetching of the `has-one` and `belongs-to` related entities\n+ fetch `has-many` related entities on demand\n+ built-in filters for the entity and its relations\n+ filter by properties of related entities when selecting, updating and deleting\n+ create and update aggregates in a transaction when related entities are embedded (encouraged by DDD)\n+ build derived (slightly different) queries without repeating yourself\n+ prepare entity data before saving\n+ transform entity data after fetching\n+ access to underlying Query Builder\n+ sub-queries in WHERE clause\n+ combine commands and requests for transactional execution\n+ test entities descriptions with spec when creating a repository\n\nTODO\n\n- support enums trough namespaced keywords https://www.bevuta.com/en/blog/using-postgresql-enums-in-clojure/\n- create specs for entity fields from column types\n- test for MySQL support\n\n## Usage in a nutshell\n\nPlease, use jitpack repository in order to add it as a dependency to your project:\n[![](https://jitpack.io/v/kurbatov/norm.svg)](https://jitpack.io/#kurbatov/norm).\n\n`norm`` will be available from clojars as\nsoon as the documentation is finished.\n\nAlso you want to ensure that there is a JDBC driver for the database of your choice\namong dependencies. **norm** supports H2 and PostgreSQL as of now, so check if at least\none of the following is present:\n\n```clojure\n[com.h2database/h2 \"2.1.214\"]\n[org.postgresql/postgresql \"42.2.12\"]\n```\n\nRequire the core namespace of **norm** into a namespace in your project:\n\n```clojure\n(ns my-project.db-component\n  (:require [norm.core :as norm]))\n\n;; or\n\n(require '[norm.core :as norm])\n```\n\nPrepare datasource to establish DB connection. Any `jdbc.next`'s `Connectable` will do.\nThat means any `javax.sql.DataSource` implementation will do too.\n\n```clojure\n;; prepare datasource the way you like it\n;; using Hikari pool, for example\n(def db (HikariDataSource.\n          (doto (HikariConfig.)\n                (.setJdbcUrl jdbc-url)\n                (.setUsername username)\n                (.setPassword password))))\n\n;; or for testing with an in-memory H2 instance\n(with-open [db (next.jdbc/get-connection {:dbtype \"h2:mem\"})]\n  ;; your code goes here\n)\n```\n\nDescribe entities and their relations in a map (let's call it *mapping*).\nThere is a helper function that builds the mapping based on foreign keys of\na provided database (described in the section below).\n\n```clojure\n(def mapping {:address {:table :addresses}})\n```\n\nSee [`doc/mapping-example.md`](doc/mapping-example.md) for the full-featured example of a mapping.\n\nBuild a `repository` using the mapping and the database connection from the previous steps.\n\n```clojure\n(def repository (norm/create-repository :sql mapping {:db db}))\n```\n\nUse entities from the repository to build queries and commands.\nAll queries get executed only with explicit `fetch!` (or `execute!` for commands)\ncall or when called with a function that has the `!` suffix.\n\n```clojure\n(let [page 0\n      size 40\n      sort :role\n      clause {:active true}\n      query (-\u003e (norm/find (:employee repository) clause)\n                ;; (norm/where clause) may be here when clause is not specified above\n                (norm/order sort)\n                (norm/skip (* page size))\n                (norm/limit size))\n      total (norm/fetch-count! query) ;; fetches total number of the records as if limit were not specified\n      employees (norm/fetch! query)]  ;; fetches entities according to the query\n  {:total total\n   :employees employees})\n```\n\nAdjust and combine commands before executing and execute them.\n\nFor example, that's how you would execute a command:\n\n```clojure\n(-\u003e (:user repository)\n    (norm/create {:login \"admin\"})\n    norm/execute!)\n\n;; or\n\n(norm/create! (:user repository) {:login \"admin\"})\n```\n\nIf you want multiple commands executed in the same transaction it may be achieved using `then` function:\n\n```clojure\n(let [{:keys [user person]} repository]\n  (-\u003e (norm/create user {:login \"admin\"})\n      (norm/then (norm/create person {:name \"John Doe\"}))\n      (norm/then #(norm/create-relation user (:id (first $)) :person (:id (second $))))\n      norm/execute!))\n```\n\nUnary function may be used as a command inside a transaction. It receives a vector of previous commands' results\nand is supposed to return a command (or query) built upon those data which are unknown before actual execution.\n\n## Writing a Mapping\n\nThe mapping is a regular clojure map that enlists entities of your domain keyed by entity names.\n\n**norm** is easy to use on an existing database schema. You can automate writing of the mapping\nusing provided helper method:\n\n```clojure\n(require '[norm.sql.helper :refer [find-entities]])\n(find-entities {:db db :schema \"public\"}) ; :schema may be omitted for auto-detection\n```\n\nThis helper method works only for H2 and PostgreSQL currently.\n\nThe autogenerated entity description may suffer from weird naming of entities and their relations\ndue to lack of context. It doesn't handle pluralization of irregular nouns (such as `person` - `people`).\nIt may miss relations when an entity references another entity more than once. But it provides a good\nstarting point to build a perfect version of the mapping.\n\nManual writing of the mapping from scratch is totally fine. Below you will find description of mapping's parts.\n\nDatabase identifiers for table names and fields are supposed to be in `kebab-case`.\nThose converted to `snake_case` when building SQL query.\n\nFor a mapping example see [`doc/mapping-example.md`](doc/mapping-example.md).\n\nThe mapping has to contain at least one entity.\n\n### Entity\n\nThe only required property of an entity in the mapping is `:table`. It specifies which table of the databases\ncontains main data that consists the entity.\n\nThe following optional properties are supported:\n\n- `:pk` - primary key column name (defaults to `:id` when absent)\n- `:fields` - data fields of the entity (defaults to all columns of the table when absent)\n- `:filter` - imposes filtering rule on the rows of the table in case not every row represents an instance of the entity\n- `:prepare` - an unary function that modifies an instance before saving\n- `:transform` - an unary function that modifies an instance after fetching from storage (TODO consider renaming to :post-fetch)\n- `:rels` - a map that describes relations of the entity to other entities\n\n### Relations\n\nSome entities may have bound or related entities.\n\nRelations of an entity are described with a map where keys stand for names of the relations and values\n(which are maps as well) convey the essence of the relation. Required properties of a relation\nare `:entity` (the main entity is related to), `:type` (of the relation) and `:fk` (stands for *foreign key*).\n\n**norm** supports following relation types:\n\n#### `:has-one`\n\nThis kind of relationship tells that the entity is an aggregate root (main entity) for another subordinate entity.\n\n`attachment` `has-one` `file` means that there can be only one `file` bound to an `attachment`.\n\nIn the database that would look the following way: `file` has the `attachment-id` property that refers to the `id`\nproperty in `attachment`'s storage.\n\n```clojure\n{:attachment {:table :attachments\n              :rels  {:file {:type   :has-one\n                             :entity :file\n                             :fk     :attachment-id}}}\n :file       {:table :files}}\n```\n\n#### `:has-many`\n\nThis relationship allows the entity to aggregate several instances of a subordinate entity.\n\n`post` `has-many` `attachments` means that there might be more than one `attachment` for a `post`.\n\nIn the database it would look the following way: `attachment` has the `post-id` property that refers to the `id`\nproperty of `post`'s storage.\n\n```clojure\n{:post       {:table :posts\n              :rels  {:attachments {:type   :has-many\n                                    :entity :attachment\n                                    :fk     :post-id}}}\n :attachment {:table :attachments}}\n```\n\n#### `:belongs-to`\n\nThis kind of relationship is reciprocal to the previous two. It tells that the entity is a subordinate part of another\nentity.\n\n`attachment` `belongs-to` `post` means that `attachment` has the `post-id` property that refers to the `id` property in\n`post`'s storage. It doesn't say anything about how many attachments per post is allowed.\n\n```clojure\n{:attachment {:table :attachments\n              :rels  {:post {:type   :belongs-to\n                             :entity :post\n                             :fk     :post-id}}}\n :post       {:table :posts}}\n```\n\n#### Many-to-many Relationship\n\nIn case two entities aggregate each other's instances, they are in many-to-many relationship. Entities of this\nrelationship are both aggregate roots and we cannot tell that one is a subordinate of another.\n\nIn the database, two entities are joined through an intermediary table that stores identifiers of both of them.\nSuch table goes to `:join-table` in the description of a relation. When description of a relation includes\n`:join-table` it must include `:rfk` (stands for *relation's foreign key*) - a column in the join-table\nwhich contains identifiers of related entity.\n\n`employee` `has-many` `responsibilities` and `responsibility` in its turn `has-many` `employees`.\nThey relate through the join-table `employees-responsibilities`.\n\n```clojure\n{:employee       {:table :employees\n                  :rels {:responsibilities {:entity :responsibility\n                                            :type :has-many\n                                            :fk :employee-id\n                                            :join-table :employees-responsibilities\n                                            :rfk :responsibility-id}}}\n :responsibility {:table :responsibilities\n                  :rels {:employees {:entity :employee\n                                     :type :has-many\n                                     :fk :responsibility-id\n                                     :join-table :employees-responsibilities\n                                     :rfk :employee-id}}}}\n```\n\n#### Optional Properties of Relation\n\n- `:filter` - clause to filter related entities\n- `:eager` - when true, **norm** fetches a related entity eagerly (`false` by default).\nWorks only for `:has-one` and `:belongs-to` relationships.\n\n## Repository\n\nRepository is a collection of entities backed by a data source.\n\nYou can create a repository of chosen type using `norm/create-repository` function.\n\n```clojure\n;; creating a SQL repository\n(def repository (norm/create-repository :sql entities {:db db}))\n```\n\nNow **norm** implements only SQL repositories which are backed by relational databases.\nI would love to implement support for graph databases (Neo4j for example) and other kinds of\nnon-relational databases just to check if the general concept works fine with them.\n\nYou can create a derived repository using the following methods: `norm/add-entity`, `norm/except` and\n`norm/only`. Please, refer to their documentation for details. Derived repositories can be handed over\nto different parts of an application so that they cannot construct queries for certain entities.\n\n`norm/transaction` allows creating a new transaction for a repository and use this object to collect\nseveral queries and commands for transactional execution. Commands and queries may be appended into\nthe transaction using `conj` or `norm/then`.\n\n```clojure\n(-\u003e (norm/transaction repository)\n    (conj (norm/create (:user repository) {:login \"admin\"}))\n    (norm/then (norm/create (:person repository) {:name \"John Doe\"}))\n    norm/execute!)\n```\n\n## Building Queries and Commands\n\nMost of the **norm**'s core functions generate queries and commands which do not execute immediately.\nInstead you can pass and modify those objects between different functions of an application before\nthey reach the right place to be actually executed. That gives us an opportunity to split the application\nto pure functional (free from side-effects) and imperative (with side-effects) parts. All the functions\nwhich cause side-effects have an exclamation mark `!` at the end of their names.\n\nWhat is the benefit of **norm** commands and queries instead of just plain old SQL queries in strings?\n**norm** represents them as structured objects which can be analyzed and adjusted so that the final query\ncan be built in several independent steps. **norm**'s queries allow building derived queries without\nrepeating yourself and writing two slightly different queries. If you want to change one little piece\nin the middle of a query, it is easier to achieve modifying a structured object than trying to split\na string in the right places and recombine it correctly.\n\n### Queries\n\nYou can create a query using `norm/find` function specifying the entity you want and optionally\na list of fields you are interested in and filtration criteria. `norm/fetch!` performs the query\nand fetches matching entities from the database. You can also use `norm/find!` in order to fetch\nthe same entities immediately.\n\n```clojure\n(def active-users (norm/find (:user repository) {:active true}))\n(def users (norm/fetch! active-users))\n;; or\n(norm/find! (:user repository) {:active true})\n;=\u003e [{:id 1, :login \"admin\", :active true}, ...]\n```\n\nIf you already have a base query where you would like to specify an additional filtration criteria, you\ncan use `norm/where` function. The additional criteria is conjunct with the existing criterion using logical `and`.\n\n```clojure\n(-\u003e active-users\n    (norm/where {:id [\u003c 3]})\n    norm/fetch!)\n;=\u003e [{:id 1, ...}, {:id 2, ...}]\n```\n\nWhen you need a certain instance of an entity and its id is known, you can use `norm/fetch-by-id!` to fetch that in\nshort line of code.\n\n```clojure\n(norm/fetch-by-id! (:user repository) 5)\n;=\u003e {:id 5, ...}\n```\n\nIn order to fetch only related entities of an aggregate root, you can use `norm/find-related` function.\n\n```clojure\n;; getting personal info of a user with a known login\n(-\u003e (:user repository)\n    (norm/find-related :person {:login \"jdoe\"})\n    norm/fetch!)\n;=\u003e {:id 3, :name \"John Doe\"}\n```\n\n### Commands\n\nCommands, by contrast to the queries, modify data in the storage. Similar to queries, commands do not\ntake effect immediately. In order to execute a command, use `norm/execute!` function or one of\nthe following functions with an exclamation mark at the end.\n\n`norm/create` allows creating a new instance of an entity in the backing storage.\n\n```clojure\n(-\u003e (:user repository)\n    (norm/create {:login \"guest\"})\n    norm/execute!)\n;=\u003e {:id 42}\n\n;; or this way for brevity\n(norm/create! (:user repository) {:login \"guest\"})\n```\n\n`norm/create` accepts an aggregate with relations of the entity as the value. This way related entities are created\nin the same transaction as the aggregate root. In case if the related entity already exists and its id provided,\na relation between newly created entity and the existing one will be created in the same transaction.\n\n```clojure\n;; this call creates a new user and a related person in the same transaction\n(-\u003e (:user repository)\n    (norm/create {:login \"john\", :person {:name \"John Doe\"}})\n    norm/execute!)\n;=\u003e {:id 43}\n\n;; this call creates a new user and a relation to an existing person by the person's id in one transaction\n(-\u003e (:user repository)\n    (norm/create {:login \"jane\", :person {:id 63}})\n    norm/execute!)\n;=\u003e {:id 44}\n```\n\n`norm/update` updates existing entities in the storage. It takes a patch that will be applied to matched entities\nand a filtration clause as the arguments and returns a number of updated instances.\n\n```clojure\n;; activate a user with id = 42\n(-\u003e (:user repository)\n    (norm/update {:active true} {:id 42})\n    norm/execute!)\n;=\u003e 1\n```\n\n`norm/delete` removes entities from the storage. It takes a filtration clause as the argument and returns\na number of removed entities.\n\n```clojure\n;; remove all inactive users\n(let [user (:user repository)]\n  (-\u003e (norm/delete user {:active false})\n      norm/execute!))\n;=\u003e 99\n```\n\nYou can manage relations between existing entities using `norm/create-relation` and `norm/delete-relation`\nfunctions. They are especially useful for management of [many-to-many relationships](#many-to-many-relationship).\n\n### Adjusting Entities\n\nYou can modify existing entity or create a derived one with the following functions.\n\n`norm/with-filter` applies a default filter to all the queries for specified entity.\nFor example, it may be useful in order to make different entities which reside in the same table\nbut can be distinguished using some discriminator field.\n\n```clojure\n;; let's find top 10 incoming and outgoing documents\n(let [document (:document repository)\n      incoming (-\u003e document (norm/with-filter {:type \"incoming\"}))\n      outgoing (-\u003e document (norm/with-filter {:type \"outgoing\"}))]\n  {:incoming (-\u003e (norm/find incoming) (norm/limit 10) norm/fetch!)\n   :outgoing (-\u003e (norm/find outgoing) (norm/limit 10) norm/fetch!)})\n```\n\n`norm/with-rels` amends relations of the specified entity (adds new or replaces an old one with the same name).\n\n ```clojure\n (def user-with-personal-data\n      (norm/with-rels (:user repository)\n                      {:person {:entity :person\n                                :type   :has-one\n                                :fk     :user-id\n                                :eager  true}}))\n ```\n\n`norm/with-eager` makes specified relations to be fetched eagerly (works only for `:has-one` and\n`:belongs-to` relations). It is just a special case of `norm/with-rels` which sets `true` to\nthe `:eager` property of specified relations which already exist.\n\n```clojure\n(-\u003e (:user repository)\n    (norm/with-eager [:person])\n    (norm/where {:id 1})\n    (norm/fetch!))\n;=\u003e {:login \"admin\", :person {:name \"John Doe\"}}\n```\n\n### Low-Level Query Builder\n\nRequire the sql namespace of **norm** into a namespace in your project:\n\n```clojure\n(ns my-project.db-component\n  (:require [norm.sql :as sql]))\n\n;; or\n\n(require '[norm.sql :as sql])\n```\n\nThere are four functions to build different kinds of queries: `sql/insert`, `sql/update`,\n`sql/select` and `sql/delete`.\nThey take a database object as the first argument and a table-name keyword as the second.\nThe rest of the arguments differs for all of them so, please, refer to their docs for the details.\n\nA query object can be compiled into SQL statement with `str` call:\n\n```clojure\n(-\u003e (sql/select db :users [:id :login])\n    (norm/where {:active true\n                 :role \"admin\"})\n    str)\n;=\u003e \"SELECT id AS \\\"id\\\", login AS \\\"login\\\" FROM users WHERE (active IS true AND role = ?)\"\n```\n\n### Transactional Execution\n\n`norm/then` allows combining multiple commands and queries into a single transaction.\n\n```clojure\n;; 3 users get created in the same transaction\n(let [{:keys [user]} repository]\n    (-\u003e (norm/create user {:login \"john\"})\n        (norm/then (norm/create user {:login \"jane\"}))\n        (norm/then (norm/create user {:login \"bob\"}))\n        norm/execute!))\n```\n\nYou can use a unary function as a command inside a transaction. It receives a vector of previous commands' results\nand is supposed to return a command (or query) built upon the data unknown before actual execution.\n\n```clojure\n(let [{:keys [user person]} repository]\n  (-\u003e (norm/create user {:login \"john\"})\n      (norm/then (norm/create person {:name \"John Doe\"}))\n      (norm/then #(norm/create-relation user (:id (first $)) :person (:id (second $))))\n      norm/execute!))\n```\n\nBy default the result of transaction execution is a vector of individual commands' and queries' results.\n\nIn case when one function generates a command/transaction and another executes it and processes the result,\nrefactoring of the first function is complicated as additional commands in the transaction change the resulting vector\nof `norm/execute!`. You can explicitly manage the result of transaction execution.\n\nAssigning `{:tx-result true}`to metadata of one of the commands/queries in a transaction makes it the result provider of\nthe whole transaction.\n\n```clojure\n;; making result of the last query in the transaction the result of the whole transaction\n(let [{:keys [user]} repository\n      create-john (norm/create user {:login \"john\"})\n      create-jane (norm/create user {:login \"jane\"})\n      select-john-and-jane (norm/find user {:login [\"john\" \"jane\"]})]\n  (-\u003e create-john\n      (norm/then create-jane)\n      (norm/then (vary-meta select-john-and-jane assoc :tx-result true))\n      norm/execute!))\n```\n\n`:tx-result-fn` metadata of a transaction object allows even more flexibility in modification of a transaction result\nbefore return from the `norm/execute!` function. It allows providing a unary function that receives a vector of results\nof individual commands in a transaction at the end of the execution and modifies the final result of the transaction.\n\n```clojure\n;; transaction execution returns a vector of id for newly created entities\n(let [{:keys [user]} repository\n      create-john (norm/create user {:login \"john\"})\n      create-jane (norm/create user {:login \"jane\"})]\n  (-\u003e create-john\n      (norm/then create-jane)\n      (vary-meta assoc :tx-result-fn #(mapv :id %))\n      norm/execute!))\n```\n\nTODO nested transactions and `:tx-propagation`.\n\n## Tips\n\nIf you want to use your own datatype as a parameter then the idiomatic approach of\nimplementing `next.jdbc`'s `SettableParameter` protocol is enough.\n\n### How To Use `or` in `where`\n\nTODO using vector for a list of choices\n\nTODO using `:or` in a where-map\n\nTODO using `'(or ...)`\n\nTODO prefixing identifiers when building query with the name of an entity and `^:exact` in where and order\n\n## Clear Exit Plan\n\nIf `norm` does not quite work for you in some scenarios, it is possible to get down a couple of layers of abstraction.\n\nConsider the [low level query builder](#low-level-query-builder) in order to compose and execute SQL queries using\n`norm` DSL. This DSL is used internally by the high-level API. It enables you defining your very own high-level API that\nfits your case using already existing building blocks.\n\nIf you ever want to write a raw SQL query as a plain string, there is\n[`next.jdbc`](https://github.com/seancorfield/next-jdbc) that can execute it. `norm` uses it as a transitive dependency\nin order to execute queries composed with high-level DSL. You can mix and match both of them in your project as you see\nfit and completely migrate from one to another when time comes.\n\n## Similar Solutions\n\nIf **norm** is not quite what you are looking for, there are other libraries similar in some ways\nwhich I can positively or negatively recommend to spare you some time reading docs and testing.\n\nIn the world of lots of slightly different solutions, I wish all of them included such section\ninto their documentation.\n\n[seql](https://github.com/exoscale/seql) may be a better fit if you want to add predefined set of\nmutations into the entity model.\n\n[Toucan](https://github.com/metabase/toucan) looks really close to **norm** in its intentions.\nIt is older and has a company behind it. So the library probably have seen more usage in production.\nOn the negative side of things, Toucan seems to be too object oriented for a Clojure library:\nit requires defining a record for every entity out there.\n\n[Hyperion](https://github.com/8thlight/hyperion) allows defining entities however dealing with\nrelations may be tricky.\n\n[Relational Mapper](https://github.com/netizer/relational_mapper) offers a lightweight solution for\nquerying related entities based on [HoneySQL](https://github.com/seancorfield/honeysql).\nIt has a namespace with functions to perform DB migrations based on the mapping.\nHowever it looks like creation, modification and deletion of entities is out of scope for this library.\n\nI recommend you to skip [Korma](https://github.com/korma/Korma) which is mentioned in every clojure-related\nresource for beginners. Korma is great for simple tasks but it is long abandoned and lacks some features\nthat come in handy even in a moderately complex project. Maybe someone takes over the project in the future\nand makes it great again. For now I can only say many thanks to Korma for the inspiration it gave me.\nI made **norm** to be a better version of Korma.\n\n## License\n\nCopyright © 2020-2022\n\nThis program and the accompanying materials are made available under the\nterms of the The MIT License (MIT).\n\nSee [LICENSE](https://github.com/kurbatov/norm/blob/master/LICENSE) file\nfor the full tex of the license.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkurbatov%2Fnorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkurbatov%2Fnorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkurbatov%2Fnorm/lists"}