{"id":24870977,"url":"https://github.com/pepzer/shrimp","last_synced_at":"2025-10-15T14:31:47.371Z","repository":{"id":62434612,"uuid":"107287303","full_name":"pepzer/shrimp","owner":"pepzer","description":"A ClojureScript library targeting Node.js and providing async channels on top of Red Lobster promise library.","archived":false,"fork":false,"pushed_at":"2017-11-02T15:15:16.000Z","size":44,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-01T01:18:33.708Z","etag":null,"topics":["clojure","clojurescript","clojurescript-library","javascript","lumo","nodejs"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pepzer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-17T15:23:20.000Z","updated_at":"2019-10-24T15:48:49.000Z","dependencies_parsed_at":"2022-11-01T21:15:46.332Z","dependency_job_id":null,"html_url":"https://github.com/pepzer/shrimp","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/pepzer/shrimp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepzer%2Fshrimp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepzer%2Fshrimp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepzer%2Fshrimp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepzer%2Fshrimp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pepzer","download_url":"https://codeload.github.com/pepzer/shrimp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepzer%2Fshrimp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279085462,"owners_count":26100017,"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","status":"online","status_checked_at":"2025-10-15T02:00:07.814Z","response_time":56,"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","clojurescript-library","javascript","lumo","nodejs"],"created_at":"2025-02-01T04:18:55.390Z","updated_at":"2025-10-15T14:31:47.102Z","avatar_url":"https://github.com/pepzer.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"## What is Shrimp?\n\nShrimp is a [ClojureScript](https://clojurescript.org/) library that implements asynchronous communication channels, built on top of [Red Lobster](https://github.com/whamtet/redlobster) promise library.  \nIt targets [Node.js](https://nodejs.org/en/) and [Lumo](https://github.com/anmonteiro/lumo) with the aim to be lightweight and to offer in addition useful functionalities for testing async functions.  \nThis is an experiment and there is no plan to offer more functionalities and no attempt to hide the underlying promise API.  \nTo work with [core.async](https://github.com/clojure/core.async) on Lumo check [Andare](https://github.com/mfikes/andare). \n\nAlmost all operations on channels in Shrimp return a Red Lobster promise that could be managed with functions and macros from the same library.  \nIn addition Shrimp offers an async supporting loop macro, and there is also [Shrimp-Chain](https://github.com/pepzer/shrimp-chain), a collection of macros built on top of Shrimp providing a unified way to manage multiple async operations returning promises intertwined with synchronous ones.\n\n## Leiningen/Clojars/Lumo\n\n[![Clojars Project](https://img.shields.io/clojars/v/shrimp.svg)](https://clojars.org/shrimp)\n\nIf you use [Leiningen](https://github.com/technomancy/leiningen) add redlobster and shrimp to the dependencies in your project.clj file.\n\n```clojure\n:dependencies [... \n               [org.clojars.pepzer/redlobster \"0.2.2\"]\n               [shrimp \"0.1.1-SNAPSHOT\"]]\n```\n    \nFor Lumo you could either download the dependencies with Leiningen/Maven and specify the libraries on the CLI this way:\n\n    $ lumo -D org.clojars.pepzer/redlobster:0.2.2,shrimp:0.1.1-SNAPSHOT\n    \nOr you could download the jar files and add them to Lumo classpath:\n\n    $ lumo -c redlobster-0.2.2.jar:shrimp-0.1.0.jar \n    \n## Note on Red Lobster\n\nThe release of Red Lobster listed above is my version, the reason is a small fix that avoids annoying (especially with Lumo) warnings on compilation.  \nI will send a pull request with this fix and if the official release gets updated i will switch to that as a dependency.\n\n## REPL\n\nTo run a REPL in the project directory you could either use lein figwheel (optionally with rlwrap):\n   \n    $ rlwrap lein figwheel dev\n\nWith Node.js and npm installed open a shell, navigate to the root of the project and run:\n\n    $ npm install ws\n    $ node target/out/shrimp.js\n\nThen the REPL should connect in the lein figwheel window.\n\nWith Lumo installed just run the lumo-repl.cljsh script:\n   \n    $ bash lumo-repl.cljsh\n    \nThis will run the REPL and will also listen on the port 12345 of the localhost for connections.  \nYou could connect with Emacs and inf-clojure-connect.\n\nThe examples from this readme could be evaluated in the REPL easily by opening the file `repl_session.cljs`.\n\n## Usage\n\nTo use shrimp, require the shrimp.core namespace, and create a channnel:\n \n```clojure\n(require '[shrimp.core :as sc])\n\n(def chan1 (sc/chan))\n```\n    \nTo close a channel:\n\n```clojure\n(sc/close! chan1)\n```\n\nA closed channel allows to take! until the values-queue is empty, then it switches to dead, to test the channel state:\n\n```clojure\n(sc/closed? chan1)\n\n(sc/dead? chan1)\n```\n\nA channel has a buffer-size that defines the maximum dimension of the queue for both put! and take! operations.  \nAfter the number of puts or takes reaches the buffer-size, a new call will fail (i.e. return a promise already realised to respectively false and nil for put! and take!/alts!).  \nThe default limit for the buffer is 1024, a different value could be specified on creation:\n\n```clojure\n(def chan2 (sc/chan 20))\n```\n\n### put! and take!\n\nRequire Red Lobster macros with use-macros to manage the channel promises:\n\n```clojure\n(require '[shrimp.core :as sc])\n(use-macros '[redlobster.macros :only [let-realised]])\n\n                                        ; Define the channel\n(def chan1 (sc/chan))\n\n                                        ; Try to take from the channel\n                                        ; Print the value when the promise is realised\n(let-realised [prom (sc/take! chan1)]\n  (do (println \"Val: \" @prom)\n      (sc/close! chan1)))\n\n                                        ; Put a value inside the channnel\n(sc/put! chan1 \"foo\")\n\n;; =\u003e Val: foo\n```\n\n### alts!\n\nThere is an alts! function to take from the first available channel with an optional timeout and corresponding default value:\n\n```clojure\n(require '[shrimp.core :as sc])\n(use-macros '[redlobster.macros :only [let-realised]])\n\n                                        ; Define the channels\n(def chan1 (sc/chan))\n(def chan2 (sc/chan))\n\n                                        ; Try to take from both channels\n                                        ; Print the value when the promise is realised\n(let-realised [prom (sc/alts! [chan1 chan2])]\n  (let [[v ch] @prom] \n    (if (= ch chan1)\n      (println \"Val: \" v \", from chan1\")\n      (println \"Val: \" v \", from chan2\"))\n    (sc/close! chan1)))\n\n                                        ; Put a value inside the channnel\n(sc/put! chan1 \"foo\")\n\n;; =\u003e Val: foo , from chan1\n```\n\nTo define a timeout of 1 second and a default value on expiration:\n\n```clojure\n(sc/alts! [chan1 chan2] 1000 \"default value\")\n```\n\n### defer-loop\n\nThis macro mimics Clojure's loop, but it allows to use async code returning promises inside the loop.  \n`defer-loop` does not consume the stack and does not define global vars, it allows the keyword `:delay` in the bindings followed by a value (in milliseconds) that will be used to defer the recursion.  \n`defer-loop` returns a promise realised to the exit value of the loop.\n\n```clojure\n(require '[shrimp.core :as sc])\n(use-macros '[redlobster.macros :only [when-realised]])\n(use-macros '[shrimp.macros :only [defer-loop]])\n\n                                        ; Define the channel\n(def chan1 (sc/chan))\n\n                                        ; The loop stops when the take! promise realises to nil\n(defer-loop [prom (sc/take! chan1) :delay 100]\n  (when-realised [prom]\n    (if @prom\n      (do\n        (println \"Val:\" @prom \", from defer-loop\")\n\n                                        ; defer-recur works like recur\n                                        ; It is only defined under the scope of the defer-loop macro\n        (defer-recur (sc/take! chan1)))\n\n      (println \"Exit from the loop\"))))\n\n                                        ; Put a value inside the channnel\n(sc/put! chan1 \"foo\")\n(sc/put! chan1 \"bar\")\n\n(sc/close! chan1)\n\n;; =\u003e Val: foo , from defer-loop\n;; =\u003e Val: bar , from defer-loop\n;; =\u003e Exit from the loop\n```\n\n\n## Extras and Testing\n\nThere are other macros in shrimp that might be useful in particular for testing.\n\n### defer\n\nThis is a slight variation on the Red Lobster's defer macro, it allows to defer the execution of an expression with a delay.  \nCompared to Red Lobster, this version always requires the integer value for the delay, but now it could be a var in addition to a literal number, also if the delay is any negative value defer will use js/setImmediate.\n\n```clojure\n(use-macros '[shrimp.macros :only [defer]])\n\n(defer 2000 (println \"foo\"))\n\n;; =\u003e foo\n```\n\n### realise-time\n\nThis macro takes an expression returning a redlobster promise and prints the elapsed time once the promise is realised.  \n`realise-time` returns a promise realised to the same value as the wrapped expression:\n\n```clojure\n(use-macros '[shrimp.macros :only [realise-time defer-loop]])\n\n(realise-time\n  (defer-loop [x 0 :delay 100]\n    (if (\u003c x 10)\n      (defer-recur (inc x))\n      (println \"x:\" x))))\n\n;; =\u003e #object[redlobster.promise.Promise]\n;; =\u003e x: 10\n;; =\u003e \"Elapsed time: 1006.799804 msecs\"\n```\n\n### defer-time\n\nThis small macro allows to easily print the elapsed time for an asynchronous function. The function do-time is defined inside the scope of the macro, it should be called as the last expression in the asynchronous block (or as a wrapper for it):\n\n```clojure\n(use-macros '[shrimp.macros :only [defer-time defer]])\n\n(defer-time \n  (defer 2000 (do (println \"foo\")\n                  (do-time (println \"bar\")))))\n\n;; =\u003e foo\n;; =\u003e bar\n;; =\u003e \"Elapsed time: 2003.601113 msecs\"\n```\n\n### Testing asynchronous functions\n\nShrimp provides a macro and the necessary helper functions to run asynchronous tests and receive correct error reports. The differences compared to standard tests are minimal.  \nFirst create a test namespace to run all the other tests with the run-async-tests macro:\n\n```clojure\n(ns foo.all-tests\n  (:require [foo.core-test]\n            [foo.bar-test])\n  (:use-macros [shrimp.test.macros :only [run-async-tests]]))\n\n(defn -main []\n  (run-async-tests\n   foo.core-test\n   foo.bar-test))\n```\n\nThe only addition to the tests is a call to the done! function at the end of each deftest, a call to done! is required for *ALL* tests even for synchronous ones:\n\n```clojure\n(ns foo.core-test\n  (:require [cljs.test :refer [deftest is]]\n            [shrimp.test :as st])\n  (:use-macros [shrimp.macros :only [defer]]))\n\n(deftest sync-test\n  (is (= 3 (+ 1 2)))\n  (st/done!))\n\n                                        ; Call st/done! as the last expression in the async block\n(deftest async-test\n  (defer 2000 (do\n                (is (= 1 1))\n                (st/done!))))\n```\n\nTo run the tests invoke the main of foo.all-tests, for example with Lumo:\n\n\t$ lumo -c ... -m foo.all-tests\n\nThe output should be similar to this:\n\n```\nTesting foo.core-test\n\nRan 2 tests containing 2 assertions.\n0 failures, 0 errors.\n\nTesting foo.bar-test\n\nRan 2 tests containing 2 assertions.\n0 failures, 0 errors.\n\nAll namespaces:\n\nRan 4 tests containing 4 assertions.\n0 failures, 0 errors.\n```\n\n## Tests\n\nTo run the tests with Leiningen use:\n\n```\n$ lein cljsbuild test\n```\n\nWith Lumo:\n\n```\n$ bash lumo-test.sh\n```\n\n## Code Maturity\n\nThis is an early release, hence bugs should be expected and future releases could break the current API.\n\n## Contacts\n\n[Giuseppe Zerbo](https://github.com/pepzer), [giuseppe (dot) zerbo (at) gmail (dot) com](mailto:giuseppe.zerbo@gmail.com).\n\n## License\n\nCopyright © 2017 Giuseppe Zerbo.  \nDistributed under the [Mozilla Public License, v. 2.0](http://mozilla.org/MPL/2.0/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpepzer%2Fshrimp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpepzer%2Fshrimp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpepzer%2Fshrimp/lists"}