{"id":32192111,"url":"https://github.com/clojurewerkz/eep","last_synced_at":"2025-10-22T01:58:41.362Z","repository":{"id":7297173,"uuid":"8613361","full_name":"clojurewerkz/eep","owner":"clojurewerkz","description":"Embedded Event Processing in Clojure","archived":false,"fork":false,"pushed_at":"2016-01-02T02:03:17.000Z","size":152,"stargazers_count":140,"open_issues_count":1,"forks_count":7,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-10-18T19:20:04.857Z","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/clojurewerkz.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.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":"2013-03-06T21:16:30.000Z","updated_at":"2023-11-17T12:26:39.000Z","dependencies_parsed_at":"2022-09-22T12:22:23.720Z","dependency_job_id":null,"html_url":"https://github.com/clojurewerkz/eep","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/clojurewerkz/eep","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Feep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Feep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Feep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Feep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojurewerkz","download_url":"https://codeload.github.com/clojurewerkz/eep/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Feep/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280365594,"owners_count":26318385,"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-21T02:00:06.614Z","response_time":58,"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":[],"created_at":"2025-10-22T01:58:36.001Z","updated_at":"2025-10-22T01:58:41.355Z","avatar_url":"https://github.com/clojurewerkz.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EEP, Embedded Event Processing in Clojure\n\nEEP is a Clojure library for embedded event processing.\nIt combines a lightweight generic event handling system,\nand with multiple windowed stream operations.\n\neep-clj is heavily influenced by other EEP projects:\n\n  * [eep-js (JavaScript)](https://github.com/darach/eep-js)\n  * [eep-erl (Erlang)](https://github.com/darach/eep-erl)\n  * [eep-php (PHP)](https://github.com/ianbarber/eep-php)\n\n\n## Project Maturity\n\nEEP is a *young* and evolving project. The API may change\nsignificantly in the near future, so use it at your own discretion.\n\nThis section will be update as the project matures.\n\n\n## Maven Artifacts\n\n### Most Recent Release\n\nWith Leiningen:\n\n    [clojurewerkz/eep \"1.0.0-beta1\"]\n\nWith Maven:\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003eclojurewerkz\u003c/groupId\u003e\n      \u003cartifactId\u003eeep\u003c/artifactId\u003e\n      \u003cversion\u003e1.0.0-beta1\u003c/version\u003e\n    \u003c/dependency\u003e\n\n\n## Documentation \u0026 Examples\n\n### Quickstart\n\nIn order to create an emitter, use `clojurewerkz.eep.emitter/create` function:\n\n```clj\n(ns user\n  (:require [clojurewerkz.eep.emitter :as eem]))\n\n(def emitter (eem/create {}))\n```\n\nYou can register event handlers on an emitter by using handler helper\nfunctions. For example, in order to calculate sums for even and odd\nnumbers, you can first define a `splitter` and then two `aggregators`,\none for even and one for odd ones:\n\n```clj\n(eem/defsplitter emitter :entrypoint (fn [i] (if (even? i) :even :odd)))\n\n(eem/defaggregator emitter :even (fn [acc i] (+ acc i)) 0)\n(eem/defaggregator emitter :odd (fn [acc i] (+ acc i)) 0)\n```\n\nHere, `:entrypoint`, `:even` and `:odd` are event types, unique event\nidentifiers.\n\nIn order to push data to emitter, use `clojurewerkz.eep.emitter/notify`,\nwhich takes an emitter, event type and payload:\n\n```clj\n(eem/notify emitter :entrypoint 1)\n(eem/notify emitter :entrypoint 1)\n(eem/notify emitter :entrypoint 1)\n(eem/notify emitter :entrypoint 4)\n```\n\nYou can then view the state of an aggregator like so:\n\n```clj\n(eem/state (eem/get-handler emitter :odd)) ;; 3\n(eem/state (eem/get-handler emitter :even)) ;; 4\n```\n\n## Core Concepts\n\n  * `Emitter` is responsible for handler registration and event\n    routing. It holds everything together.\n\n  * `Event`s are dispatched by user code. An event is an\n    arbitrary tuple of user-defined structure. There's no validation\n    provided for it.\n\n  * `Event Type` is a unique event type identifier, used for routing. It can\n    be a number, a symbol, a keyword, a string or anything else. All the events\n    coming into `Emitter` have a type.\n\n  * `Handler` is a function and optional state attached to it. The function\n    acts as a callback, executed whenever an event is matched on the type.\n    The same handler can be used for multiple event types, but\n    an event type can only have one handler at most.\n\n## Handler types\n\nEach handler is attached to emitter with a `type`, which uniquely\nidentifies it within an emitter. You can only attach a single handler\nfor any given `type`. However, you can attach a single Handler to\nmultiple `types`.\n\nHandlers may be stateful and stateless. `filter`, `splitter`,\n`transformer`, `multicast` and `observer` are __stateless__. On the\nother hand, `aggregator`, `buffer` and `rollup` are __stateful__.\n\n### Stateful Handlers\n\n`aggregator` is initialized with initial value, then gets events of\na certain type and aggregates state by applying aggregate function to\ncurrent state and an incoming event. It's similar to `reduce`\nfunction in Clojure, except for it's applied to the stream of data.\n\n```clj\n(def emitter (eem/create {})) ;; create the emitter\n(eem/defaggregator\n  emitter ;; the emitter\n  :accumulator ;; the event type to attach to\n  (fn [acc i] (+ acc i)) ;; the function to apply to the stream\n  0) ;; the initial state\n;; send 0-9 down the stream\n(doseq [i (range 10)]\n  (eem/notify emitter :accumulator i))\n\n;; state is 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9\n(eem/state (eem/get-handler emitter :accumulator)) ;; 45\n```\n\n`buffer` receives events of a certain type and stores them in a\ncircular buffer with given capacity. As soon as capacity is\nreached, it drops events (first in, first out).\n\n```clj\n(def emitter (eem/create {}))\n(eem/defbuffer\n  emitter ;; the emitter\n  :entry ;; the event type to attach to\n  5) ; the maximum values.\n;; send 0-9 down the stream\n(doseq [i (range 10)]\n  (eem/notify emitter :entry i))\n\n;; 0 1 2 3 4 were dropped, in that order.\n(eem/state (eem/get-handler emitter :entry)) ; [5 6 7 8 9]\n```\n\n`rollup` acts in a manner similar to buffer, except for it's\ntime-bound but not capacity-bound, so whenever a time period is\nreached, it dispatches all the events to several other handlers.\n\n```clj\n;; Aggregates events for 100 milliseconds and emits them to :summarizer\n;; as soon as timespan elapsed\n(eem/defrollup emitter :rollup-entry 100 :summarizer)\n```\n\n### Stateless Handlers\n\nNote: calling `state` on a stateless handler will return `nil`.\n\n`filter` receives events of a certain type, and forwards ones for which\n`filter-fn` returns `true` to one or more other handlers:\n\n```clj\n;; Filters events going through the stream, allowing only even ones\n;; to go through\n(def emitter (eem/create {}))\n(eem/deffilter\n  emitter ;; the emitter\n  :filtered ;; the event type to attach to\n  number? ;; function to evaluate input\n  :only-numbers) ;; event-type to forward input that evaluates to true\n;; buffer to receive filtered input for example\n(eem/defbuffer emitter :only-numbers 5)\n;; send some test data down the stream\n(doseq [i [1 \"a\" 5 \"b\" 9 \"c\" \"d\" 32 \"eep\" 58]]\n  (eem/notify emitter :filtered i))\n\n;; all items where (number? item) was false were not forwarded\n(eem/state (eem/get-handler emitter :only-numbers)) ;; [1 5 9 32 58]\n```\n\n`splitter` receives events of a certain type, and dispatches them to\ntype returned by predicate function. For example, you can split stream\nof integers to even and odd ones and process them down the pipeline\ndifferently.\n\n```clj\n;; Splits event stream to two parts, routing even events with :even\n;; type and odd ones with :odd.\n(def emitter (eem/create {}))\n(eem/defsplitter\n  emitter ;; the emitter\n  :entry ;; the event type to attach to\n  (fn [i] (if (number? i) :numbers :non-numbers))) ;; function evaluates input and returns which event type to forward to.\n;; aggregator to receive the numbers for example\n(eem/defaggregator emitter :numbers + 0)\n;; buffer to receive the numbers for example\n(eem/defbuffer emitter :non-numbers 5)\n;; send some test data down the stream\n(doseq [i [1 \"a\" 5 \"b\" 9 \"c\" \"d\" 32 \"eep\" 58]]\n  (eem/notify emitter :entry i))\n\n;; all numbers are sent to :numbers, all strings are sent to :not-numbers\n(eem/state (eem/get-handler emitter :numbers)) ;; 105, which is 1 + 5 + 9 +32 + 58\n(eem/state (eem/get-handler emitter :non-numbers)) ;; [\"a\" \"b\" \"c\" \"d\" \"eep\"], which is all the non-numbers\n```\n\n`transformer` defines a transformer that gets typed tuples, applies\ntransformation function to each one of them and forwards them to\none or more other handlers. It's similar to applying `map` to\nelements of a list, except for function is applied to stream of data.\n\n```clj\n;; Transforms event stream by multiplying each event to 2\n(def emitter (eem/create {}))\n\n;; Define the transformer function\n(defn fizzbuzzer [i]\n  (cond\n    (zero? (mod i 15)) \"FizzBuzz\"\n    (zero? (mod i 5)) \"Buzz\"\n    (zero? (mod i 3)) \"Fizz\"\n    :else i))\n\n(eem/deftransformer\n  emitter ;; the emitter\n  :entry ;; the event type to attach to\n  fizzbuzzer ;; the transformer function\n  :fizzbuzz) ;; the new event type to forward to\n;; a buffer to receive output for example\n(eem/defbuffer emitter :fizzbuzz 5)\n\n;; send some test data down the stream\n(doseq [i (range 10)]\n    (eem/notify emitter :entry i))\n\n;; Anything divided by 3 is \"Fizz\", anything divided by 5 is \"Buzz\", and anything divided by 15 is \"FizzBuzz\"\n(eem/state (eem/get-handler emitter :fizzbuzz)) ;; [\"Buzz\" \"Fizz\" 7 8 \"Fizz\"]\n```\n\n\n`multicast` receives events of a certain type and broadcasts them\nto several handlers with different types. For example, whenever an\nalert is received, you may want to send notifications via email,\nIRC, Jabber and append event to the log file.\n\n```clj\n;; Redistributes incoming events, routing them to multiple other event types\n(def emitter (eem/create {}))\n(eem/defmulticast\n  emitter ;; the emitter\n  :entry ;; the event type to attach to\n  [:accumulator :incrementer :multiplier]) ;; vector of event types to forward to\n\n;; set up aggregators for example\n(eem/defaggregator emitter :accumulator (fn [acc i] (+ acc i)) 0)\n(eem/defaggregator emitter :incrementer (fn [acc i] (+ acc 1)) 0)\n(eem/defaggregator emitter :multiplier (fn [acc i] (* acc i)) 1)\n\n;; send test data down the stream\n(doseq [i [2 3 4]]\n  (eem/notify emitter :entry i))\n\n(eem/state (eem/get-handler emitter :accumulator)) ;; 9, 2 + 3 + 4\n(eem/state (eem/get-handler emitter :incrementer)) ;; 3, 1 + 1 + 1\n(eem/state (eem/get-handler emitter :multiplier)) ;; 24, 2 * 3 * 4\n\n;; It's also possible to attach additional multicast entries. This will\n;; append :subtractor to the list of streams broadcasted by :entry from that point forward\n(eem/defmulticast emitter :entry [:subtractor])\n(eem/defaggregator emitter :subtractor (fn [acc i] (- acc i)) 0)\n(eem/notify emitter :entry 2)\n\n(eem/state (eem/get-handler emitter :accumulator)) ;; 11, 2 + 3 + 4 + 2\n(eem/state (eem/get-handler emitter :incrementer)) ;; 4, 1 + 1 + 1 + 1\n(eem/state (eem/get-handler emitter :multiplier)) ;; 48, 2 * 3 * 4 * 2\n(eem/state (eem/get-handler emitter :subtractor)) ;; -2\n```\n\n`observer` receives events of a certain type and runs function\n(potentially with side-effects) on each one of them.\n\n```clj\n(def emitter (eem/create {}))\n\n;; our function with side effects\n(defn announcer [item]\n  (println (str \"I would like to announce: \" item)))\n\n(eem/defobserver emitter :announce announcer)\n\n(eem/notify emitter :announce \"This Item\")\n;; prints \"I would like to announce: This Item\"\n```\n\n## Topology DSL\n\nThere's a DSL that threads emitter through all handler declarations,\nin order to create aggregation topologies in a more concise and obvious\nway:\n\n```clj\n(def emitter\n  (eem/build-topology (eem/create {})\n                      :entry (eem/defsplitter (fn [i] (if (even? i) :even :odd)))\n                      :even (eem/defbuffer 5)\n                      :odd  (eem/defbuffer 5)))\n\n(doseq [i (range 10)]\n  (eem/notify emitter :entry i))\n\n(eem/state (eem/get-handler emitter :even)) ;; [0 2 4 6 8]\n(eem/state (eem/get-handler emitter :odd)) ;; [1 3 5 7 9]\n```\n\nAlternatively, you can use Clojure `-\u003e` for creating concise topologies:\n\n```clj\n(def emitter\n  (-\u003e (eem/create {})\n      (eem/defsplitter :entry (fn [i] (if (even? i) :even :odd)))\n      (eem/defbuffer :even 5)\n      (eem/defbuffer :odd 5)))\n\n(doseq [i (range 10)]\n  (eem/notify emitter :entry i))\n\n(eem/state (eem/get-handler emitter :even)) ;; [0 2 4 6 8]\n(eem/state (eem/get-handler emitter :odd)) ;; [1 3 5 7 9]\n```\n\n## Topology visualization\n\nYou can also visualize your topology by calling `clojurewerkz.eep.visualization/visualise-graph`\nand giving it an emitter. You'll get an image like this one:\n\n[![Topology Visualization Example](http://coffeenco.de/assets/images/topology_example.png)](http://coffeenco.de/assets/images/topology_example.png)\n\n## Windows\n\nWindows and buffers are an essential part of event processing.\nWe've added the most important implementations of windowed\noperations, such as sliding, tumbling, monotonic and timed windows\nto EEP to allow you to use them within topologies.\n\n### Sliding window\n\nSliding windows have a fixed a-priori known size.\n\nExample: Sliding window of size 2 computing sum of values.\n\n```\n    t0     t1      (emit)   t2             (emit)       tN\n  +---+  +---+---+          -...-...-\n  | 1 |  | 2 | 1 |   \u003c3\u003e    : x : x :\n  +---+  +---+---+          _...+---+---+               ...\n             | 2 |              | 2 | 3 |    \u003c5\u003e\n             +---+              +---+---+\n                                    | 4 |\n                                    +---+\n```\n\nUseful to hold last `size` elements.\n\n### Tumbling window\n\nTumbling windows (here) have a fixed a-priori known size.\n\nExample: Tumbling window of size 2 computing sum of values.\n\n```\n    t0     t1      (emit)    t2            t3         (emit)    t4\n  +---+  +---+---+         -...-...-\n  | 1 |  | 2 | 1 |   \u003c3\u003e   : x : x :\n  +---+  +---+---+         -...+---+---+   +---+---+            ...\n                                   | 3 |   | 4 | 3 |    \u003c7\u003e\n                                   +---+   +---+---+\n```\n\nUseful to accumulate `size` elements and aggregate on overflow.\n\n### Monotonic window\n\nMakes a clock tick on every call. Whenever clock is elapsed, emits to aggregator.\n\nIn essence, it's an alternative implementation of tumbling-window that allows\nto use custom emission control rather than having a buffer overflow check.\n\nUseful for cases when emission should be controlled by arbitrary function,\npossibly unrelated to window contents.\n\n### Timed window\n\nA simple timed window, that runs on wall clock. Receives events and stores them\nuntil clock is elapsed, emits for aggregation after that.\n\nIn essence, it's an alternative implementation of tumbling-window or monotonic-window\nthat allows wall clock control.\n\nUseful for accumulating events for time-bound events processing, accumulates events\nfor a certain period of time (for example, 1 minute), and aggregates them.\n\n## Busy-spin\n\nWhenever you create an emitter, you may notice that one of your cores is 100% busy. You should\nnot worry about it, since all dispatchers use a tight loop for dispatch, without sleeping, therefore\nnot yielding control back to OS, so OS defines that as 100% processor load.\n\n## Supported Clojure Versions\n\nEEP requires Clojure 1.6+.\n\n\n## Development\n\nEEP uses [Leiningen 2](https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md). Make\nsure you have it installed and then run tests against all supported Clojure versions using\n\n    lein all test\n\nThen create a branch and make your changes on it. Once you are done with your changes and all\ntests pass, submit a pull request on Github.\n\n## License\n\nCopyright © 2014-2016 Michael Klishin, Alex Petrov, and the ClojureWerkz team.\n\nDouble licensed under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html) (the same as Clojure) or\nthe [Apache Public License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojurewerkz%2Feep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojurewerkz%2Feep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojurewerkz%2Feep/lists"}