{"id":19293980,"url":"https://github.com/igrishaev/pact","last_synced_at":"2026-03-02T06:03:28.842Z","repository":{"id":43844150,"uuid":"458750651","full_name":"igrishaev/pact","owner":"igrishaev","description":"Chaining values with ease","archived":false,"fork":false,"pushed_at":"2025-02-07T13:36:25.000Z","size":42,"stargazers_count":36,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-22T07:46:50.109Z","etag":null,"topics":["clojure","clojurescript","manifold","promise"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2022-02-13T08:22:36.000Z","updated_at":"2025-02-07T13:36:29.000Z","dependencies_parsed_at":"2024-11-09T22:36:57.245Z","dependency_job_id":"0c9680e1-c199-4e3f-bd6c-65bd0e01197f","html_url":"https://github.com/igrishaev/pact","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/igrishaev/pact","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fpact","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fpact/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fpact/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fpact/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/pact/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fpact/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29993548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["clojure","clojurescript","manifold","promise"],"created_at":"2024-11-09T22:36:44.245Z","updated_at":"2026-03-02T06:03:23.833Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pact\n\nA small library for chaining values through forms. It's like a promise but much\nsimpler.\n\nSince 0.1.1, supports ClojureScript and its specific types (e.g. Promise).\n\n## Installation\n\nLein:\n\n```clojure\n[com.github.igrishaev/pact \"0.1.1\"]\n```\n\nDeps.edn\n\n```clojure\n{com.github.igrishaev/pact {:mvn/version \"0.1.1\"}}\n```\n\n## How It Works\n\nThe library declares two universe handlers: `then` and `error`. When you apply\nthem to the \"good\" values, you propagate further. Applying `error` to \"good\" things \ndoes nothing. And vice versa: `then` for the \"bad\" values does nothing, but calling\n`error` on \"bad\" values gives you a chance to recover the pipeline.\n\nBy default, there is only one \"bad\" value which is an instance of `Throwable`\n(`js/Error` in ClojureScript). Other types are considered positive ones. The\nlibrary carries extensions for such async data types as `CompletableFuture`,\n`Manifold` and `core.async`. You only need to require their modules so they\nextend the `IPact` protocol.\n\n## Examples\n\nImport `then` and `error` macros, then chain a value with the standard `-\u003e`\nthreading macro. Both `then` and `error` accept a binding vector and an\narbitrary body.\n\n```clojure\n(ns foobar\n  (:require\n   [pact.core :refer [then error]]))\n\n\n(-\u003e 42\n    (then [x]\n      (-\u003e x int str))\n    (then [x]\n      (str x \"/hello\")))\n\n\"42/hello\"\n```\n\nIf any exception pops up, the sequence of `then` handlers gets interrupted, and\nthe `error` handler gets into play:\n\n```clojure\n(-\u003e 1\n    (then [x]\n      (/ x 0))\n    (then [x]\n      (str x \"/hello\")) ;; won't be executed\n    (error [e]\n      (ex-message e)))\n\n\"Divide by zero\"\n```\n\nThe `error` handler gives you a chance to recover from the exception. If you\nreturn a non-exceptional data in `error`, the execution will proceed from the\nnext `then` handler:\n\n```clojure\n(-\u003e 1\n    (then [x]\n      (/ x 0))\n    (error [e]\n      (ex-message e))\n    (then [message]\n      (log/info message)))\n\n;; nil\n```\n\nThe `-\u003e` macro can be nested. This is useful to capture the context for a\npossible exception:\n\n```clojure\n(-\u003e 1\n    (then [x]\n      (+ x 1))\n    (then [x]\n      (-\u003e x\n          (then [x]\n            (/ x 0))\n          (error [e]\n            (println \"The x was\" x)\n            nil))))\n\n;; The x was 2\n;; nil\n```\n\nBesides `then` and `error` macros, the library provides the `then-fn` and\n`error-fn` functions. They are useful when you have a ready function that\nprocesses the value:\n\n```clojure\n(ns foobar\n  (:require\n   [pact.core :refer [then-fn error-fn]]))\n\n(-\u003e 1\n    (then-fn inc)\n    (then-fn str))\n\n;; \"2\"\n\n(-\u003e 1\n    (then [x]\n      (/ x 0))\n    (error-fn ex-message))\n\n;; \"Divide by zero\"\n```\n\nChaining with `then` and `error` is especially good for maps as allowing\ndestructuring:\n\n```clojure\n(-\u003e {:db {...} :cassandra {...}}\n\n    ;; Get a user from the database and attach it to the scope.\n    (then [{:as scope :keys [db]}]\n      (let [user (jdbc/get-by-id db :users 42)]\n        (assoc scope :user user)))\n\n    ;; Having a user, get their last items from Cassandra cluster\n    ;; and attach them to the scope.\n    (then [{:as scope :keys [cassandra user]}]\n      (let [items (get-user-items cassandra user)]\n        (assoc scope :items items)))\n\n    ;; Do something more...\n    (then [...]\n      ...))\n```\n\n## Fast fail\n\nTo interrupt the chain of `then` handlers, either throw an exception or use the\n`failure` function which is just a shortcut for raising a exception. The\nfunction takes a map or a message with a map:\n\n```clojure\n(ns foobar\n  (:require\n   [pact.core :refer [then error failure]]))\n\n(-\u003e 1\n    (then [x]\n      (if (not= x 42)\n        (failure \"It was not 42!\" {:x x})\n        (+ 1 x)))\n    (error-fn ex-data))\n\n;; {:x 1 :ex/type :pact.core/failure}\n```\n\n## Supported types\n\nThe `core` namespace declares the `then` and `error` handlers for the `Object`,\n`Throwable`, and `java.util.concurrent.Future` types. The `Future` values get\ndereferenced when passing to `then`.\n\nThe following modules extend the `IPact` protocol for asynchronous types.\n\n### Completable Future (Clojure)\n\nThe module `pact.comp-future` handles the `CompletableFuture` class available\nsince Java 11. The module also provides its own `future` macro to build an\ninstance of `CompletableFuture`:\n\n```clojure\n(-\u003e (future/future 1)\n    (then [x]\n      (inc x))\n    (then [x]\n      (/ 0 0))\n    (error [e]\n      (ex-message e))\n    (deref))\n\n\"Divide by zero\"\n```\n\nPay attention: if you fed an instance of `CompletableFuture` to the threading\nmacro, the result will always be of this type. Thus, there is a `deref` call at\nthe end.\n\nInternally, the `then` handler calls for the `.thenApply` method if a future and\nthe `error` handler boils down to `.exceptionally`.\n\n### Manifold (Clojure)\n\nThe `pact.manifold` module makes the handlers work with the amazing Manifold\nlibrary and its types. The Pact library doesn't have Manifold dependency: you've\ngot to add it on your own.\n\n```clojure\n[manifold \"0.1.9-alpha3\"]\n```\n\n```clojure\n(-\u003e (d/future 1)\n    (then [x]\n      (/ x 0))\n    (error [e]\n      (ex-message e))\n    (deref))\n\n\"Divide by zero\"\n```\n\nUnder the hood, `then` and `error` handlers call the `d/chain` and `d/catch`\nmacros respectively.\n\nOnce you've put an instance of Manifold deferred, the result will always be a\n`Deferred`.\n\n### Core.async (Clojure + ClojureScript)\n\nTo make the library work with `core.async` channels, import the\n`pact.core-async` module:\n\n```clojure\n(ns foobar\n  (:require\n   [pact.core :refer [then error]]\n   [pact.core-async]\n   [clojure.core.async :as a]))\n```\n\nLike Manifold, the `core.async` dependency should be added by you as well:\n\n```clojure\n[org.clojure/core.async \"1.5.648\"]\n```\n\nNow you can chain channels through the `then` and `error` actions. Internally,\neach handler takes exactly one value from a source channel and returns a new\nchannel with the result. For `then`, exceptions traverse the channels being\nuntouched. And instead, the `error` handler ignores ordinary values and affects\nonly exceptions. Quick demo:\n\n```clojure\n(let [in (a/chan)\n      out (-\u003e in\n              (then [x]\n                (/ x 0))\n              (error [e]\n                (ex-message e))\n              (then [message]\n                (str \"\u003c\u003c\u003c \" message \" \u003e\u003e\u003e\")))]\n\n  (a/put! in 1)\n\n  (a/\u003c!! out) )\n\n;; \"\u003c\u003c\u003c class java.lang.String cannot be cast ...\"\n```\n\n### JS Promise (ClojureScript)\n\nFor a JS promise, `then` and `error` handlers resolve to its `.then` and\n`.catch` methods:\n\n```clojure\n(-\u003e (js/Promise.resolve 1)\n    (then-fn inc)\n    (then [x]\n      (js/console.log x)))\n```\n\nA better example with fetching an HTTP resource:\n\n```clojure\n(-\u003e (js/fetch \"https://some.api.com/data.json\")\n    (then [response]\n      (.json response))\n    (then [data]\n      ...)\n    (error [e]\n      (js/console.log ...)))\n```\n\n\n## Testing\n\nTo run both Clojure and ClojureScript tests, execute `make test-all`. For the\nClojureScript tests, you need Node.js installed.\n\n\u0026copy; 2022 Ivan Grishaev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fpact","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fpact","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fpact/lists"}