[
  {
    "path": ".gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n.hgignore\n.hg/\n.idea/\n*.iml\n.DS_Store\n*.log\ntest2junit/\nbuild.xml\nresources/public/js/\nout/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: clojure\nscript: lein doo node cljs-test once\njdk:\n  - oraclejdk11"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).\n\n## [0.6.41] - 2019-05-09\n### Changed\n- dependencies\n- core.async as dev dependency, needs to be specifically depended on\n\n## [0.6.40] - 2019-05-09\n### Changed\n- missing spec\n\n## [0.6.39] - 2019-05-09\n### Changed\n- `:schedule/new` and `:schedule/delete` message types\n- dependencies\n\n## [0.6.38] - 2018-12-19\n### Changed\n- Clojure 1.10.0\n- dependencies\n\n## [0.6.37] - 2018-07-08\n### Changed\n- dependencies\n- tests with Clojure 1.10.0-alpha6\n- adapted specs\n\n## [0.6.36] - 2018-06-04\n### Changed\n- dependencies\n\n## [0.6.35] - 2018-05-23\n### Changed\n- dependencies\n\n## [0.6.34] - 2018-02-27\n### Changed\n- assign system-id, node-type, and node-id (useful for application clusters)\n\n## [0.6.33] - 2018-02-05\n### Changed\n- component shutdown function\n\n## [0.6.32] - 2018-01-24\n### Changed\n- deps\n\n## [0.6.30] - 2018-01-24\n### Changed\n- improved metadata handling in scheduler\n\n## [0.6.29] - 2018-01-05\n### Changed\n- record tag creation timestamp\n\n## [0.6.28] - 2018-01-04\n### Changed\n- only keep last two component ids in cmp-seq when dispatched from scheduler\n\n## [0.6.27] - 2017-11-28\n### Changed\n- specs for messages without payload\n\n## [0.6.26] - 2017-11-15\n### Changed\n- Make UUID's pass uuid? predicate\n- PR #48 from kamituel\n\n## [0.6.25] - 2017-11-15\n### Changed\n- Clojure 1.9 RC1; deps\n\n## [0.6.24] - 2017-10-26\n### Changed\n- filter identity on component maps set\n\n## [0.6.23] - 2017-10-26\n### Changed\n- test with Clojure 1.9 beta 3\n\n## [0.6.22] - 2017-10-24\n### Changed\n- expound messages on firehose\n\n## [0.6.21] - 2017-10-13\n### Changed\n- expound for human friendly spec validation errors\n\n## [0.6.20] - 2017-10-12\n### Changed\n- pull request #47 from kamituel merged\n- see PR for details\n\n## [0.6.19] - 2017-10-04\n### Changed\n- latest Clojure and ClojureScript in tests\n\n## [0.6.18] - 2017-10-02\n### Changed\n- fixed cljs tests\n\n## [0.6.17] - 2017-09-20\n### Changed\n- record processing time\n\n## [0.6.16] - 2017-09-19\n### Changed\n- run test with Clojure 1.9.0-beta1\n\n## [0.6.15] - 2017-09-15\n### Changed\n- wrapped put-fn\n\n## [0.6.13] - 2017-09-03\n### Changed\n- cmp sequence in msg metadata \n\n## [0.6.12] - 2017-09-01\n### Changed\n- shutdown when encountering any exception during component init\n\n## [0.6.11] - 2017-08-24\n### Changed\n- latest dependencies\n\n## [0.6.10] - 2017-08-01\n### Changed\n- latest deps\n\n## [0.6.9] - 2017-05-30\n### Changed\n- Make switchboard's spec validation be configurable (from kamituel, PR #43)\n- latest deps\n- latest Clojure and ClojureScript after spec split\n- replace all occurrences of `clojure.spec` with `clojure.spec.alpha`\n- replace all occurrences of `cljs.spec` with `cljs.spec.alpha`\n\n## [0.6.8] - 2017-04-25\n### Changed\n- Include message type in a log statement for invalid handler return. (from kamituel, PR #41)\n\n## [0.6.7] - 2017-04-13\n### Changed\n- tests with latest ClojureScript\n\n## [0.6.6] - 2017-03-15\n### Changed\n- tests with Clojure 1.9.0-alpha15\n\n## [0.6.5] - 2017-02-24\n### Changed\n- latest core.async & other deps\n\n## [0.6.4] - 2017-01-16\n### Changed\n- put-fn also accepts a vector of messages\n\n## [0.6.3] - 2017-01-09\n### Changed\n- fixed NPE when using empty vector in :emit-msg\n\n## [0.6.2] - 2016-11-25\n### Changed\n- Fix for issue #38\n\n## [0.6.1] - 2016-11-23\n### Changed\n- moving away from alpha status\n- Clojure 1.9 just works, and because of clojure.spec, you should adopt it, too\n\n## [0.6.1-alpha11] - 2016-11-12\n### Changed\n- improved error handling\n\n## [0.6.1-alpha10] - 2016-11-09\n### Changed\n- shutdown handler in scheduler\n\n## [0.6.1-alpha9] - 2016-11-02\n### Changed\n- improvements in scheduler\n\n## [0.6.1-alpha8] - 2016-10-13\n### Changed\n- latest dependencies\n\n## [0.6.1-alpha7] - 2016-09-21\n### Changed\n- tests with Clojure 1.9.0-alpha12\n- latest core.async\n\n## [0.6.1-alpha6] - 2016-08-24\n### Changed\n- tests with Clojure 1.9.0-alpha11\n\n## [0.6.1-alpha5] - 2016-08-20\n### Changed\n- improved error handling in msg-handler-loop\n\n## [0.6.1-alpha4] - 2016-08-01\n### Changed\n- Formatting\n- Firehose message handling improved\n\n## [0.6.1-alpha3] - 2016-08-01\n### Changed\n- Firehose message ID added\n\n## [0.6.1-alpha2] - 2016-07-12\n### Changed\n-  Clojure 1.9.0-alpha10\n\n## [0.6.1-alpha1] - 2016-07-06\n### BREAKING CHANGES\n- Clojure 1.9 required\n- component IDs MUST be namespaced keywords\n- message types MUST be namespaced keywords\n\n## [0.5.22] - 2016-06-06\n### Changed\n- support multiple messages in `send-to-self` and `emit-msg`. `emit-msgs` deprecated\n\n## [0.5.20] - 2016-06-04\n### Changed\n- dependencies; no dependency on specific Clojure version\n\n## [0.5.19] - 2016-05-28\n### Changed\n- components support `observed-xform` function: applied to observed snapshot before resetting the local observed state\n\n## [0.5.18] - 2016-05-11\n### Changed\n- `:emit-msgs` and `:send-to-self` in return map of handler\n\n## [0.5.15] - 2016-03-30\n### Changed\n- Clojure 1.8, ClojureScript 1.8.40, dependencies\n\n## [0.5.14] - 2016-03-08\n### Changed\n- handler functions can now be free from side effects\n\n## [0.5.13] - 2016-03-05\n### Changed\n- additional tests; checking for overhead introduced by library\n\n## [0.5.12] - 2016-03-04\n### Changed\n- library now testable on JVM and browser\n\n## [0.5.11] - 2016-03-03\n### Changed\n- performance improvements in browser by using requestAnimationFrame more sparingly\n\n## [0.5.10] - 2016-02-21\n### Changed\n- some refactoring and additional test\n\n## [0.5.9] - 2016-02-19\n### Changed\n- broader use of blocking put; additional test\n\n## [0.5.8] - 2016-02-17\n### Changed\n- send-msg blocks by default; component tests\n\n## [0.5.7] - 2016-01-29\n### Changed\n- PR from clyfe: stoppable scheduler\n\n## [0.5.1] - 2016-01-03\n### Changed\n- Sente and Reagent/UI components moved into separate repos\n\n## [0.4.11] - 2015-12-30\n### Changed\n- Kafka consumer and producer moved from systems-toolbox into separate repo\n\n## [0.4.10] - 2015-12-29\n### Changed\n- allow specifying a user-id-fn for sente (from PR)\n\n## [0.4.9] - 2015-12-27\n### Changed\n- Kafka producer and consumer components\n\n## [0.4.8] - 2015-12-22\n### Changed\n- fwd-as-w-meta function from PR\n\n## [0.4.7] - 2015-12-22\n### Changed\n- allow user-specified state-pub-handler in views, for example for resetting state on logout\n\n## [0.4.6] - 2015-12-21\n### Changed\n- fn for sending message to single component\n\n## [0.4.5] - 2015-12-17\n### Changed\n- alternative sente config; version bumps\n\n## [0.4.2] - 2015-12-07\n### Changed\n- minor histogram improvements\n\n## [0.4.1] - 2015-12-05\n### BREAKING CHANGES\n- state-fn now needs to return a map, where the fresh component state is expected under the `:state` key. In addition, an optional `:shutdown-fn` can be specified, which will be called when the component is shutdown or restarted. This is for example useful when resources such as web servers or database connections need to be shut down.\n\n\n## [0.3.15] - 2015-12-03\n### Changed\n- documentation; dependency upgrades; Clojure 1.8.0-RC3 in sample\n\n## [0.3.14] - 2015-11-30\n### Changed\n- histogram more configurable\n\n## [0.3.12] - 2015-11-26\n### Changed\n- more reasonable x-axis increments\n\n## [0.3.11] - 2015-11-09\n### Changed\n- latest version of ClojureScript (much faster compiles)\n\n## [0.3.10] - 2015-10-31\n### Changed\n- simplified component wiring\n\n## [0.3.9] - 2015-10-30\n### Changed\n- allow passing of middleware to the sente component, besides the index-page-fn\n\n## [0.3.8] - 2015-10-28\n### Changed\n- version bumps, including core.async v0.2.371\n\n## [0.3.7] - 2015-10-26\n### Changed\n- fix for exception when putting too many messages at once\n\n## [0.3.6] - 2015-10-15\n### Changed\n- optional message filtering via predicate function (see example)\n\n## [0.3.1] - 2015-10-13\n### Changed\n- component initialization can now also be handled by the switchboard. See commit message and sample.\n\n\n## [0.2.31] - 2015-10-12\n### Changed\n- unhandled handler: this function is called for each message that is not handled by another handler in the :handler-map of a component.\n\n## [0.2.30] - 2015-09-23\n### Changed\n- version bumps\n\n## [0.2.29] - 2015-08-29\n### Changed\n- Buffer WS messages until the connection is opened.\n\n## [0.2.28] - 2015-08-28\n### Changed\n- host and port configuration via environment variables (e.g. for use with Docker)\n\n## [0.2.27] - 2015-08-21\n### Changed\n- dependency updates & web server options\n\n## [0.2.26] - 2015-08-10\n### Changed\n- Immutant-web instead of http-kit: as far as open source projects go, http-kit does not look very healthy.\n- Less logging: the log component is probably not very useful at all. There's no good reason not to use 'conventional' logging inside handler code. Being able to inspect input and output messages without recompile should greatly reduce the need for logging anyway.\n\n## [0.2.25] - 2015-08-08\n### Changed\n- refactoring, more readable component namespace\n\n## [0.2.24] - 2015-08-06\n### Changed\n- recording the sequence of handling components\n- dependency bumps\n\n## [0.2.23] - 2015-08-06\n### Changed\n- UUIDs sent as strings\n\n## [0.2.22] - 2015-08-05\n### Changed\n- Only non-firehose messages are wrapped when putting on firehose channel\n\n## [0.2.21] - 2015-08-04\n### Changed\n- messages that are sent between component started and system completely wired are kept\n\n## [0.2.20] - 2015-07-30\n### Changed\n- Expose metadata explicitly in firehose messages\n\n## [0.2.19] - 2015-07-30\n### Changed\n- Reagent components now support :lifecycle-callbacks parameter that can be used to attach React components' lifecycle methods such as :component-will-update.\n\n## [0.2.18] - 2015-07-28\n### Changed\n- When a message is first emitted, a :tag UUID is attached to the metadata, which allows tracking a message on its way through the system. Also, a correlation UUID is attached, which uniquely marks an emitted message.\n- The full sequence of components that a message passes through is recorded on the metadata.\n\n## [0.2.17] - 2015-07-23\n### Changed\n- View components can call init-fn on initialization. This can for example be useful when attaching a watcher to the local state atom.\n\n## [0.2.16] - 2015-07-21\n### Changed\n- no requirement for scheduler id, default is the keyword in the first position inside message to be sent\n- switchboard prints component state for inspection when receiving [cmd/print-cmp-state cmp-id] message\n- simplification of and documentation for `route-handler`\n\n## [0.2.15] - 2015-07-09\n### Changed\n- minor rewrites after 'lein kibit'\n\n## [0.2.14] - 2015-07-09\n### Changed\n- Documentation\n\n## [0.2.13] - 2015-07-08\n### Changed\n- Reloadable components without modification, e.g. for use with Figwheel\n- Specify components that can't be reloaded, e.g. WebSockets connection component\n\n## [0.2.10] - 2015-07-06\n### Changed\n- Better error handling and logging in message handler loops\n- Using aviso/pretty for exception logging in example\n\n## [0.2.9] - 2015-07-05\n### Changed\n- Figwheel in example\n- Publish state snapshot on reload\n\n## [0.2.8] - 2015-07-03\n### Changed\n- Reader conditionals instead of the now-deprecated CLJX\n\n## [0.2.7] - 2015-07-01\n### Changed\n- Clojure 1.7 final instead of release candidate\n- observer component tweaks\n\n## [0.2.6] - 2015-06-24\n### Changed\n- Custom state snapshot transformer function used in switchboard. With that, routing snapshots to server no longer fails. Minimal functionality, state snapshots from switchboard should include more information, not just component keys.\n- Observer component feeds entirely off of messages on firehose; no need for subscribing to switchboard state.s\n\n## [0.2.5] - 2015-06-23\n### Changed\n- Custom state snapshot function: allows stripping state of functions, channels and the like. Particularly useful in switchboard where state snapshots could otherwise not traverse the WebSockets connection between client and server.\n- Should the full state with non-serializable values such as channels ever be needed, there could still be a message for retrieving the full state map.\n\n## [0.2.4] - 2015-06-19\n### Changed\n- Enable handler maps inside UI components. This allows for UI components that are more independent and don't require an external store/state component. This feature can be useful for components that do not share any state with other components.\n"
  },
  {
    "path": "LICENSE",
    "content": "THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC\nLICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM\nCONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.\n\n1. DEFINITIONS\n\n\"Contribution\" means:\n\na) in the case of the initial Contributor, the initial code and\ndocumentation distributed under this Agreement, and\n\nb) in the case of each subsequent Contributor:\n\ni) changes to the Program, and\n\nii) additions to the Program;\n\nwhere such changes and/or additions to the Program originate from and are\ndistributed by that particular Contributor. A Contribution 'originates' from\na Contributor if it was added to the Program by such Contributor itself or\nanyone acting on such Contributor's behalf. Contributions do not include\nadditions to the Program which: (i) are separate modules of software\ndistributed in conjunction with the Program under their own license\nagreement, and (ii) are not derivative works of the Program.\n\n\"Contributor\" means any person or entity that distributes the Program.\n\n\"Licensed Patents\" mean patent claims licensable by a Contributor which are\nnecessarily infringed by the use or sale of its Contribution alone or when\ncombined with the Program.\n\n\"Program\" means the Contributions distributed in accordance with this\nAgreement.\n\n\"Recipient\" means anyone who receives the Program under this Agreement,\nincluding all Contributors.\n\n2. GRANT OF RIGHTS\n\na) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free copyright license to\nreproduce, prepare derivative works of, publicly display, publicly perform,\ndistribute and sublicense the Contribution of such Contributor, if any, and\nsuch derivative works, in source code and object code form.\n\nb) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free patent license under\nLicensed Patents to make, use, sell, offer to sell, import and otherwise\ntransfer the Contribution of such Contributor, if any, in source code and\nobject code form.  This patent license shall apply to the combination of the\nContribution and the Program if, at the time the Contribution is added by the\nContributor, such addition of the Contribution causes such combination to be\ncovered by the Licensed Patents. The patent license shall not apply to any\nother combinations which include the Contribution. No hardware per se is\nlicensed hereunder.\n\nc) Recipient understands that although each Contributor grants the licenses\nto its Contributions set forth herein, no assurances are provided by any\nContributor that the Program does not infringe the patent or other\nintellectual property rights of any other entity. Each Contributor disclaims\nany liability to Recipient for claims brought by any other entity based on\ninfringement of intellectual property rights or otherwise. As a condition to\nexercising the rights and licenses granted hereunder, each Recipient hereby\nassumes sole responsibility to secure any other intellectual property rights\nneeded, if any. For example, if a third party patent license is required to\nallow Recipient to distribute the Program, it is Recipient's responsibility\nto acquire that license before distributing the Program.\n\nd) Each Contributor represents that to its knowledge it has sufficient\ncopyright rights in its Contribution, if any, to grant the copyright license\nset forth in this Agreement.\n\n3. REQUIREMENTS\n\nA Contributor may choose to distribute the Program in object code form under\nits own license agreement, provided that:\n\na) it complies with the terms and conditions of this Agreement; and\n\nb) its license agreement:\n\ni) effectively disclaims on behalf of all Contributors all warranties and\nconditions, express and implied, including warranties or conditions of title\nand non-infringement, and implied warranties or conditions of merchantability\nand fitness for a particular purpose;\n\nii) effectively excludes on behalf of all Contributors all liability for\ndamages, including direct, indirect, special, incidental and consequential\ndamages, such as lost profits;\n\niii) states that any provisions which differ from this Agreement are offered\nby that Contributor alone and not by any other party; and\n\niv) states that source code for the Program is available from such\nContributor, and informs licensees how to obtain it in a reasonable manner on\nor through a medium customarily used for software exchange.\n\nWhen the Program is made available in source code form:\n\na) it must be made available under this Agreement; and\n\nb) a copy of this Agreement must be included with each copy of the Program.\n\nContributors may not remove or alter any copyright notices contained within\nthe Program.\n\nEach Contributor must identify itself as the originator of its Contribution,\nif any, in a manner that reasonably allows subsequent Recipients to identify\nthe originator of the Contribution.\n\n4. COMMERCIAL DISTRIBUTION\n\nCommercial distributors of software may accept certain responsibilities with\nrespect to end users, business partners and the like. While this license is\nintended to facilitate the commercial use of the Program, the Contributor who\nincludes the Program in a commercial product offering should do so in a\nmanner which does not create potential liability for other Contributors.\nTherefore, if a Contributor includes the Program in a commercial product\noffering, such Contributor (\"Commercial Contributor\") hereby agrees to defend\nand indemnify every other Contributor (\"Indemnified Contributor\") against any\nlosses, damages and costs (collectively \"Losses\") arising from claims,\nlawsuits and other legal actions brought by a third party against the\nIndemnified Contributor to the extent caused by the acts or omissions of such\nCommercial Contributor in connection with its distribution of the Program in\na commercial product offering.  The obligations in this section do not apply\nto any claims or Losses relating to any actual or alleged intellectual\nproperty infringement. In order to qualify, an Indemnified Contributor must:\na) promptly notify the Commercial Contributor in writing of such claim, and\nb) allow the Commercial Contributor tocontrol, and cooperate with the\nCommercial Contributor in, the defense and any related settlement\nnegotiations. The Indemnified Contributor may participate in any such claim\nat its own expense.\n\nFor example, a Contributor might include the Program in a commercial product\noffering, Product X. That Contributor is then a Commercial Contributor. If\nthat Commercial Contributor then makes performance claims, or offers\nwarranties related to Product X, those performance claims and warranties are\nsuch Commercial Contributor's responsibility alone. Under this section, the\nCommercial Contributor would have to defend claims against the other\nContributors related to those performance claims and warranties, and if a\ncourt requires any other Contributor to pay any damages as a result, the\nCommercial Contributor must pay those damages.\n\n5. NO WARRANTY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON\nAN \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER\nEXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR\nCONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A\nPARTICULAR PURPOSE. Each Recipient is solely responsible for determining the\nappropriateness of using and distributing the Program and assumes all risks\nassociated with its exercise of rights under this Agreement , including but\nnot limited to the risks and costs of program errors, compliance with\napplicable laws, damage to or loss of data, programs or equipment, and\nunavailability or interruption of operations.\n\n6. DISCLAIMER OF LIABILITY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY\nCONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION\nLOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE\nEXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY\nOF SUCH DAMAGES.\n\n7. GENERAL\n\nIf any provision of this Agreement is invalid or unenforceable under\napplicable law, it shall not affect the validity or enforceability of the\nremainder of the terms of this Agreement, and without further action by the\nparties hereto, such provision shall be reformed to the minimum extent\nnecessary to make such provision valid and enforceable.\n\nIf Recipient institutes patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Program itself\n(excluding combinations of the Program with other software or hardware)\ninfringes such Recipient's patent(s), then such Recipient's rights granted\nunder Section 2(b) shall terminate as of the date such litigation is filed.\n\nAll Recipient's rights under this Agreement shall terminate if it fails to\ncomply with any of the material terms or conditions of this Agreement and\ndoes not cure such failure in a reasonable period of time after becoming\naware of such noncompliance. If all Recipient's rights under this Agreement\nterminate, Recipient agrees to cease use and distribution of the Program as\nsoon as reasonably practicable. However, Recipient's obligations under this\nAgreement and any licenses granted by Recipient relating to the Program shall\ncontinue and survive.\n\nEveryone is permitted to copy and distribute copies of this Agreement, but in\norder to avoid inconsistency the Agreement is copyrighted and may only be\nmodified in the following manner. The Agreement Steward reserves the right to\npublish new versions (including revisions) of this Agreement from time to\ntime. No one other than the Agreement Steward has the right to modify this\nAgreement. The Eclipse Foundation is the initial Agreement Steward. The\nEclipse Foundation may assign the responsibility to serve as the Agreement\nSteward to a suitable separate entity. Each new version of the Agreement will\nbe given a distinguishing version number. The Program (including\nContributions) may always be distributed subject to the version of the\nAgreement under which it was received. In addition, after a new version of\nthe Agreement is published, Contributor may elect to distribute the Program\n(including its Contributions) under the new version. Except as expressly\nstated in Sections 2(a) and 2(b) above, Recipient receives no rights or\nlicenses to the intellectual property of any Contributor under this\nAgreement, whether expressly, by implication, estoppel or otherwise. All\nrights in the Program not expressly granted under this Agreement are\nreserved.\n\nThis Agreement is governed by the laws of the State of New York and the\nintellectual property laws of the United States of America. No party to this\nAgreement will bring a legal action under this Agreement more than one year\nafter the cause of action arose. Each party waives its rights to a jury trial\nin any resulting litigation.\n"
  },
  {
    "path": "README.md",
    "content": "# systems-toolbox\n\nApplications are systems. Systems are fascinating entities, and one of their characteristics is that we can observe them. Read more about that **[here](doc/systems-thinking.md)**. Also make sure you read about the **[rationale](doc/rationale.md)** behind this library.\n\n[![Dependencies Status](https://jarkeeper.com/matthiasn/systems-toolbox/status.svg)](https://jarkeeper.com/matthiasn/systems-toolbox)\n\n\n## What's in the box?\n\nThis library helps you build distributed systems. Such a larger system could, for example, consist of multiple processes in different **JVM**, plus all connected browser instances, which, if you think about it, are an important part of the overall distributed systems. Going forward, there is also support planned for native apps. Except for a different presentation layer, the required code should be the exact same as for single-page web applications.\n\nThis library only contains the bare minimum for building and wiring systems. Additional functionality can be found in these repositories:\n\n* **[systems-toolbox-ui](https://github.com/matthiasn/systems-toolbox-ui)**: This library gives you a simple way to build user interfaces using **[Reagent](https://github.com/reagent-project/reagent)**. This provided functionality is somewhat comparable to **[React](https://facebook.github.io/react/)** & **[Redux](https://github.com/reactjs/redux)**. I'll elaborate on this soon. This library powers the user interfaces of the example applications.\n\n* **[systems-toolbox-sente](https://github.com/matthiasn/systems-toolbox-sente)**: This library connects browser-based subsystems with a backend system using WebSockets. This library contains everything you need for serving your application via **HTTP**, including support for **HTTPS**, **HTTP2**, deployment in application servers, and serving **REST** resources. This library serves the sample applications and provides the communication with their respective backends.\n\n* **[systems-toolbox-kafka](https://github.com/matthiasn/systems-toolbox-kafka)**: This library connects different systems via **[Kafka](http://kafka.apache.org/)** so that they can form a larger, distributed system.\n\n\n* **[systems-toolbox-metrics](https://github.com/matthiasn/systems-toolbox-metrics)**: This library is a small example for how functionality can be implemented across subsystems. Here, we have a server-side component which gathers some stats about the host and JVM, plus a UI \"widget\" that can be embedded in a **systems-toolbox-ui** interface. You can see it in use in both example applications.\n\n\n## Artifacts\n\nArtifacts are [released to Clojars](https://clojars.org/matthiasn/systems-toolbox).\n\nWith Leiningen, add the following dependency to your `project.clj`:\n\n[![Clojars Project](https://img.shields.io/clojars/v/matthiasn/systems-toolbox.svg)](https://clojars.org/matthiasn/systems-toolbox)\n\nIn addition, you also need to add the dependency for [core.async](https://mvnrepository.com/artifact/org.clojure/core.async), e.g. with Leiningen:\n\n    [org.clojure/core.async \"0.6.532\"]\n\n\n## Testing\n\nThis library targets both **Clojure** and **Clojurescript** and is written entirely in `.clc`. Accordingly, testing needs to happen on both the **JVM** and at least one of the **JS** runtimes out there. For testing on the JVM, you simply run:\n\n    $ lein test\n\nOn the JavaScript side, you have more options, for example:\n\n    $ lein doo node cljs-test once\n\nInstead of `once`, you can also use `auto` to run the tests automatically when changes are detected. For more information about the options, check out the documentation for **[doo](https://github.com/bensu/doo)**.\n\nBoth ways of testing run automatically on each new commit. On the **JVM**, we use **CircleCI**: [![CircleCI Build Status](https://circleci.com/gh/matthiasn/systems-toolbox.svg?&style=shield&circle-token=24e698236c3b69afa71b954d829fbb9f9fb7c34d)](https://circleci.com/gh/matthiasn/systems-toolbox)\n\nOn **TravisCI**, the tests then run in a **JS** environment, on **[Node.js](https://nodejs.org/)**: [![TravisCI Build Status](https://travis-ci.org/matthiasn/systems-toolbox.svg?branch=master)](https://travis-ci.org/matthiasn/systems-toolbox)\n\nCheck out the `circle.yml` and `.travis.yml` files when you need an example for how to set up your projects with these providers.\n\n## Examples\n\nRight now, there are two example applications:\n\n* There's an **[example project](https://github.com/matthiasn/systems-toolbox/tree/master/examples/trailing-mouse-pointer)** in this repository that visualizes WebSocket round trip delay by recording mouse moves and showing two circles at the latest mouse position. One of them is driven by a message that only makes a local round trip in the web application, and the other one is driven by a message that is sent to the server, counted and sent back to the client. Thus, you will see the delay introduced by the by the client-server-client round trip immediately when you move the mouse. Also, there are some histograms for visualizing where time is spent. There's a live example of this application **[here](http://systems-toolbox.matthiasnehlsen.com/)**.\n\n![Example Screenshot](./doc/example.png)\n\n* Then, there's the toy example I mentioned above, **[BirdWatch](https://github.com/matthiasn/BirdWatch)**. This application provided the inspiration for this library. A running demo instance can be seen **[here](http://birdwatch.matthiasnehlsen.com)**.\n\n![BirdWatch Screenshot](./doc/birdwatch.png)\n\n\n## Feedback and question\n\nPlease feel free to open issues here or in any of the related projects when you have a question. Also, you can send the author an email, but issues are generally preferred as more users would benefit from the resulting discussion. Also, there's a chat on [![Join the chat at https://gitter.im/matthiasn/systems-toolbox](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/matthiasn/systems-toolbox?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).\n\n\n## Contributions\n\nContributions always welcome! Unless it's nothing more than the fix of a typo though, the best approach is to start with an issue and a brief discussion of the problem or improvement, rather than start the process with a pull request. Cheers.\n\n\n## Project maturity\n\nThis project is quite young and APIs may still change. However, you can expect that minor version bumps do not break your existing system. \n\n\n## License\n\nCopyright © 2015, 2016 Matthias Nehlsen\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n"
  },
  {
    "path": "circle.yml",
    "content": "machine:\n  java:\n    version: openjdk8\ntest:\n  override:\n    - lein test2junit\n  post:\n    - ant\n"
  },
  {
    "path": "dev-resources/logback-test.xml",
    "content": "<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->\n<configuration scan=\"true\" scanPeriod=\"10 seconds\">\n\n    <!-- Simple file output -->\n    <appender name=\"FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n        </encoder>\n\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- rollover daily -->\n            <fileNamePattern>logs/systems-toolbox-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy\n                    class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <!-- or whenever the file size reaches 64 MB -->\n                <maxFileSize>64 MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n\n        <!-- Safely log to the same file from multiple JVMs. Degrades performance! -->\n        <prudent>true</prudent>\n    </appender>\n\n\n    <!-- Console output -->\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>\n        </encoder>\n        <!-- Only log level INFO and above -->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>DEBUG</level>\n        </filter>\n    </appender>\n\n    <!-- Enable FILE and STDOUT appenders for all log messages.\n         By default, only log at level INFO and above. -->\n    <root level=\"INFO\">\n        <!-- appender-ref ref=\"FILE\" / -->\n        <appender-ref ref=\"STDOUT\" />\n    </root>\n\n    <logger name=\"matthiasn.systems-toolbox-kafka\" level=\"ALL\"/>\n\n</configuration>"
  },
  {
    "path": "doc/rationale.md",
    "content": "## Rationale\n\nSome time ago, I wrote this toy application called **[BirdWatch](http://github.com/matthiasn/BirdWatch)**. The first and very basic version was using **[Scala](http://www.scala-lang.org/)** on the server side and **[Knockout](https://github.com/knockout/knockout)** on the client. The next version was then using **[AngularJS](https://angularjs.org/)** on the client side, followed by another client in **[React](https://facebook.github.io/react/)**.js. Then, I fell in love with **[Clojure](http://clojure.org/)**. So I wrote the application again, this time with the backend written in Clojure and the frontend written in **[ClojureScript](https://github.com/clojure/clojurescript)**. It was the first application I had ever written in Clojure. While it worked well, it was a bit of an entangled mess (and hard to maintain). When I discovered Stuart Sierra's component library, I thought this could be a way to get more structure into my system. But it wasn't solving many of the problems that I had. Instead, I wanted to build a different kind of system, one that is primarily messaging-driven, spans multiple machines (such as web client and a server, or also multiple machines on the server side) and that uses **[core.async](https://github.com/clojure/core.async)** for message conveyance. I thought there must be another way to do it, but I didn't find an existing library. Also, at the time, the component library only worked on the server side, whereas I thought that communicating subsystems are a universal thing, not only something that one finds on a server. So I thought, why not write a library that solves my problems, one that works on both client and server? Then came along a consulting gig that allowed me to explore the problem while we wrote a commercial application with it.\n\n\n## Assumptions\n\nUnsurprisingly, the **systems-toolbox** library makes a few assumptions:\n\n* A system is made out of **subsystems**, which communicate by sending each other immutable messages via **[core.async](https://github.com/clojure/core.async)** channels, which conceptually can be seen as conveyor belts. You WILL have to either watch Rich Hickey's **[talk](http://www.infoq.com/presentations/clojure-core-async)** on this subject or read the **[transcript](https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/CoreAsync.md)** -- or both. Seriously, do that NOW.\n\n* Hey, welcome back, what do you think about the talk? I think the conveyor belt metaphor is fascinating and may well be more appropriate than in **[other places](http://www.jfs.tku.edu.tw/wp-content/uploads/2014/01/121-E01.pdf)** where it is apparently used as well. Also, I feel the implementation is ready for production usage. However, I think one piece is missing, at least when you follow the metaphor. Let me explain. Say we have a factory or the luggage transportation system in an airport and there's indeed a conveyor belt. The decoupling that comes from using this mechanism (or any other queue, for that matter) is essential for larger systems that don't make you want to pull your hair out. There's one difference, though. The conveyor belt is observable, without interfering with it -- **[core.async](https://github.com/clojure/core.async)** is not.\n\n* **Black boxes** aren't as useful as I used to think. The chances are that a portion of the taxes you pay goes into some very expensive contraptions such as CERNs **[Large Hadron Collider](http://home.cern/topics/large-hadron-collider)** that help us get a better understanding how particles interact, and rightfully so. In natural sciences, it is considered a good thing to look inside stuff (like atoms) and not stop just because someone tells you that you have to respect the borders of a black box. And yet in computer (science), we are supposed to accept that? Come on. When I come into a new project, I would like to look into all the parts of the system and see how they massage the data flowing through that very system. I do understand that implementation details of subsystems may not be important, but at least I need to understand what goes in and what goes out. And this is exactly where I think core.async falls short of the promise of building better systems. Why does the channel need to be a black box that I cannot inspect? I want to be able to see what goes onto a channel, and I want to see what is taken off a channel, just like we a can in the factory with the conveyor belt, without interfering with the system any more than necessary. Of course, everything I just mentioned only works in the realm of **functional programming**. Black boxes make a whole lot more sense when you need to protect mutable state inside objects.\n\n* Subsystems or components wired by the **systems-toolbox** are observable, they emit all messages onto a **firehose** channel, including state changes, without requiring a single extra line of code. This firehose channel contains all the messages flowing through a system, just like the **[Twitter Firehose](https://dev.twitter.com/streaming/firehose)** contains all the tweets flowing through Twitter.\n\n* Subsystems can **send** and **receive** messages. These messages are like sending your tax declaration to the IRS. You expect a response at some point, but you don't know when. Or, to stay more on topic, these are like the messages one layer below TCP. When your computer sends a datagram out, you don't know yet if anyone will respond. Maybe, who knows. If not and a timeout is reached, you will have to deal with that. One might think of this as a limitation, but I have not found that to be the case yet when building actual applications with it. Any reliable transport out there is best just effort plus timeouts plus retries and an eventual exception.\n\n* I like building systems with user interfaces. Therefore, this library also provides the building blocks for user interfaces. This part of the library is opinionated towards **[Reagent](https://github.com/reagent-project/reagent)**, as I like writing DOM subtrees in **[Hiccup](https://github.com/weavejester/hiccup)**. However, it should be simple to write building blocks for the other wrappers for React out there. If I had more time, I'd probably write those.\n"
  },
  {
    "path": "doc/systems-thinking.md",
    "content": "# Systems Thinking\n\nApplications are systems; however, don't take my word for it. Let's see how an expert on **Systems Thinking** defines a system:\n\n> \"A system isn't just any old collection of things. A system is an interconnected set of elements that is coherently organized in a way that achieves something. If you look at that definition closely for a minute, you can see that a system must consist of three kinds of things: elements, interconnections, and a function or purpose.\" - Meadows, Donatella H. (2008) Thinking in Systems: A Primer, Page 11\n\nThis applies to every meaningful application I've ever written. How do we get closer to understanding such a system? Again, here's a quote:\n\n> \"The behavior of a system is its performance over time--its growth, stagnation, decline, oscillation, randomness, or evolution.\" - Meadows, Donatella H. (2008) Thinking in Systems: A Primer, Page 88\n\nHer remarks make sense. The code itself is just the blueprint for the system. Code is like the blueprint for a busy train station versus the actual train station. You won't see where, exactly, congestion will occur until you observe bottlenecks in the living system. We need to observe and monitor a running system to understand it better.\n\nAt the same time, when writing applications using **[core.async](https://github.com/clojure/core.async)**, I find myself dealing with building blocks time and time again that have little to do with the observable logic. I've seen this with **[BirdWatch](https://github.com/matthiasn/BirdWatch)**, **[inspect](https://github.com/matthiasn/inspect)**, or also an **AngularJS markup to Hiccup conversion tool** (not published). I repeatedly wrote **[channels](http://clojure.github.io/core.async/#clojure.core.async/chan)** and **[go-loops](http://clojure.github.io/core.async/#clojure.core.async/go-loop)**. These are just incidental complexity and orthogonal to what the application is trying to solve.\n\nInstead, there should be more high-level building blocks that only handle incoming messages and potentially emit messages, as well. If you think that I am referring to the **[actor model](http://en.wikipedia.org/wiki/Actor_model)**, not so fast. Yes, **actors** have desirable properties, but I don't like that they need to know where to send messages.\n\nSo here's my idea: there are message switchboards that connect to components because we wire them inside the switchboard logic and route messages depending on the kind of message. Other than a namespaced keyword that describes the payload (potentially while checking the compliance with a schema), the switchboards do not care about the payload at all.\n\nSwitchboards then dispatch messages to the connected components. These components process messages, for example, by publishing a document to a database or answering a query or whatnot; or, such components could be additional, cascaded switchboards that, again, route messages to other components.\n\nMessage routing should be possible by matching namespaced keywords either exactly or with wildcard matches to allow for the utmost flexibility.\n\nI want to start and wire components at compile time. I also want to fire up components, wire them in a switchboard, disconnect them, or shut them down during run time. A system is a living thing, so I should be able to modify its behavior whenever I feel like it.\n\nAlso, observability needs to be an integral part of the system from the first moment on, not as an afterthought. As we can learn from the quote above, a system expresses a behavior over time. Since we want to leverage this behavior to get a better insight into the system, what could be of interest? I think waiting times until a message is getting processed and processing time for each message are suitable candidates, as well as the development of these metrics over time. Also, once we know how long it took to process an individual message, we may also want to know what the message itself was. The system should harvest these data points by default. We don't necessarily need to persist every message, but at least recent messages should be available for close inspection. The number of these depends on the available resources at any given point in time.\n\nCombine this with a built-in visualizer of the information flow. Since the structure of the application and its flow is nothing but data, we can take advantage of the **[SVG](http://en.wikipedia.org/wiki/Scalable_Vector_Graphics)** drawing capability of **[ReactJS](http://facebook.github.io/react/)** and **[Reagent](http://reagent-project.github.io)**. Any visualization always reflects the status quo of the structure of the system. When I fire up a new component at run time, this should be reflected immediately. Then, for each visualized component, there are gauges and charts that display how the components behave, both now and in the past. Also, the user interface displays incoming and outflowing data structures as desired.\n\nThese observation tools should put us in an excellent place for understanding a running system by observing its behavior. Then, we can learn more about our systems, both under real load and under simulated load, and determine where, exactly, additional effort is well spent.\n\nComponents could, for example, as already suggested, take care of database lookups. Also, they could provide a bi-directional connection between client and server over a WebSockets connection. Yet another kind of component could facilitate communication between two JVMs, e.g. using **[Kafka](http://kafka.apache.org)**, **[RabbitMQ](http://www.rabbitmq.com)**, **[Redis](http://redis.io)**, or **[HornetQ](http://hornetq.jboss.org)**.\n\nOther components encapsulate application state and surrounding business logic. The only way to interact with the application state is via messages, where it is entirely up to the state handling logic how to deal with those messages. Inside, the state is kept in an atom but this atom is not freely passed around. Only the dereferenced application state is sent back to the connected switchboard when a change occurs.\n\nOther components can render the received data as HTML using ReactJS and Reagent and emit messages back to the Switchboard when the user clicks a button, or when any other kind of interaction with the UI occurs. The state handling components can then react to the event, or the switchboard forwards a query to the server; this totally depends on how we wire up the switchboard for the particular message type.\n"
  },
  {
    "path": "examples/redux-counter01/.bowerrc",
    "content": "{\n  \"directory\": \"resources/public/bower_components\"\n}\n"
  },
  {
    "path": "examples/redux-counter01/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n.idea\n*.iml\n.DS_Store\n\nresources/public/js/build/\nresources/public/bower_components/\n*.pid\n*.out\n"
  },
  {
    "path": "examples/redux-counter01/LICENSE",
    "content": "THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC\nLICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM\nCONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.\n\n1. DEFINITIONS\n\n\"Contribution\" means:\n\na) in the case of the initial Contributor, the initial code and\ndocumentation distributed under this Agreement, and\n\nb) in the case of each subsequent Contributor:\n\ni) changes to the Program, and\n\nii) additions to the Program;\n\nwhere such changes and/or additions to the Program originate from and are\ndistributed by that particular Contributor. A Contribution 'originates' from\na Contributor if it was added to the Program by such Contributor itself or\nanyone acting on such Contributor's behalf. Contributions do not include\nadditions to the Program which: (i) are separate modules of software\ndistributed in conjunction with the Program under their own license\nagreement, and (ii) are not derivative works of the Program.\n\n\"Contributor\" means any person or entity that distributes the Program.\n\n\"Licensed Patents\" mean patent claims licensable by a Contributor which are\nnecessarily infringed by the use or sale of its Contribution alone or when\ncombined with the Program.\n\n\"Program\" means the Contributions distributed in accordance with this\nAgreement.\n\n\"Recipient\" means anyone who receives the Program under this Agreement,\nincluding all Contributors.\n\n2. GRANT OF RIGHTS\n\na) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free copyright license to\nreproduce, prepare derivative works of, publicly display, publicly perform,\ndistribute and sublicense the Contribution of such Contributor, if any, and\nsuch derivative works, in source code and object code form.\n\nb) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free patent license under\nLicensed Patents to make, use, sell, offer to sell, import and otherwise\ntransfer the Contribution of such Contributor, if any, in source code and\nobject code form.  This patent license shall apply to the combination of the\nContribution and the Program if, at the time the Contribution is added by the\nContributor, such addition of the Contribution causes such combination to be\ncovered by the Licensed Patents. The patent license shall not apply to any\nother combinations which include the Contribution. No hardware per se is\nlicensed hereunder.\n\nc) Recipient understands that although each Contributor grants the licenses\nto its Contributions set forth herein, no assurances are provided by any\nContributor that the Program does not infringe the patent or other\nintellectual property rights of any other entity. Each Contributor disclaims\nany liability to Recipient for claims brought by any other entity based on\ninfringement of intellectual property rights or otherwise. As a condition to\nexercising the rights and licenses granted hereunder, each Recipient hereby\nassumes sole responsibility to secure any other intellectual property rights\nneeded, if any. For example, if a third party patent license is required to\nallow Recipient to distribute the Program, it is Recipient's responsibility\nto acquire that license before distributing the Program.\n\nd) Each Contributor represents that to its knowledge it has sufficient\ncopyright rights in its Contribution, if any, to grant the copyright license\nset forth in this Agreement.\n\n3. REQUIREMENTS\n\nA Contributor may choose to distribute the Program in object code form under\nits own license agreement, provided that:\n\na) it complies with the terms and conditions of this Agreement; and\n\nb) its license agreement:\n\ni) effectively disclaims on behalf of all Contributors all warranties and\nconditions, express and implied, including warranties or conditions of title\nand non-infringement, and implied warranties or conditions of merchantability\nand fitness for a particular purpose;\n\nii) effectively excludes on behalf of all Contributors all liability for\ndamages, including direct, indirect, special, incidental and consequential\ndamages, such as lost profits;\n\niii) states that any provisions which differ from this Agreement are offered\nby that Contributor alone and not by any other party; and\n\niv) states that source code for the Program is available from such\nContributor, and informs licensees how to obtain it in a reasonable manner on\nor through a medium customarily used for software exchange.\n\nWhen the Program is made available in source code form:\n\na) it must be made available under this Agreement; and\n\nb) a copy of this Agreement must be included with each copy of the Program.\n\nContributors may not remove or alter any copyright notices contained within\nthe Program.\n\nEach Contributor must identify itself as the originator of its Contribution,\nif any, in a manner that reasonably allows subsequent Recipients to identify\nthe originator of the Contribution.\n\n4. COMMERCIAL DISTRIBUTION\n\nCommercial distributors of software may accept certain responsibilities with\nrespect to end users, business partners and the like. While this license is\nintended to facilitate the commercial use of the Program, the Contributor who\nincludes the Program in a commercial product offering should do so in a\nmanner which does not create potential liability for other Contributors.\nTherefore, if a Contributor includes the Program in a commercial product\noffering, such Contributor (\"Commercial Contributor\") hereby agrees to defend\nand indemnify every other Contributor (\"Indemnified Contributor\") against any\nlosses, damages and costs (collectively \"Losses\") arising from claims,\nlawsuits and other legal actions brought by a third party against the\nIndemnified Contributor to the extent caused by the acts or omissions of such\nCommercial Contributor in connection with its distribution of the Program in\na commercial product offering.  The obligations in this section do not apply\nto any claims or Losses relating to any actual or alleged intellectual\nproperty infringement. In order to qualify, an Indemnified Contributor must:\na) promptly notify the Commercial Contributor in writing of such claim, and\nb) allow the Commercial Contributor tocontrol, and cooperate with the\nCommercial Contributor in, the defense and any related settlement\nnegotiations. The Indemnified Contributor may participate in any such claim\nat its own expense.\n\nFor example, a Contributor might include the Program in a commercial product\noffering, Product X. That Contributor is then a Commercial Contributor. If\nthat Commercial Contributor then makes performance claims, or offers\nwarranties related to Product X, those performance claims and warranties are\nsuch Commercial Contributor's responsibility alone. Under this section, the\nCommercial Contributor would have to defend claims against the other\nContributors related to those performance claims and warranties, and if a\ncourt requires any other Contributor to pay any damages as a result, the\nCommercial Contributor must pay those damages.\n\n5. NO WARRANTY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON\nAN \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER\nEXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR\nCONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A\nPARTICULAR PURPOSE. Each Recipient is solely responsible for determining the\nappropriateness of using and distributing the Program and assumes all risks\nassociated with its exercise of rights under this Agreement , including but\nnot limited to the risks and costs of program errors, compliance with\napplicable laws, damage to or loss of data, programs or equipment, and\nunavailability or interruption of operations.\n\n6. DISCLAIMER OF LIABILITY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY\nCONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION\nLOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE\nEXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY\nOF SUCH DAMAGES.\n\n7. GENERAL\n\nIf any provision of this Agreement is invalid or unenforceable under\napplicable law, it shall not affect the validity or enforceability of the\nremainder of the terms of this Agreement, and without further action by the\nparties hereto, such provision shall be reformed to the minimum extent\nnecessary to make such provision valid and enforceable.\n\nIf Recipient institutes patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Program itself\n(excluding combinations of the Program with other software or hardware)\ninfringes such Recipient's patent(s), then such Recipient's rights granted\nunder Section 2(b) shall terminate as of the date such litigation is filed.\n\nAll Recipient's rights under this Agreement shall terminate if it fails to\ncomply with any of the material terms or conditions of this Agreement and\ndoes not cure such failure in a reasonable period of time after becoming\naware of such noncompliance. If all Recipient's rights under this Agreement\nterminate, Recipient agrees to cease use and distribution of the Program as\nsoon as reasonably practicable. However, Recipient's obligations under this\nAgreement and any licenses granted by Recipient relating to the Program shall\ncontinue and survive.\n\nEveryone is permitted to copy and distribute copies of this Agreement, but in\norder to avoid inconsistency the Agreement is copyrighted and may only be\nmodified in the following manner. The Agreement Steward reserves the right to\npublish new versions (including revisions) of this Agreement from time to\ntime. No one other than the Agreement Steward has the right to modify this\nAgreement. The Eclipse Foundation is the initial Agreement Steward. The\nEclipse Foundation may assign the responsibility to serve as the Agreement\nSteward to a suitable separate entity. Each new version of the Agreement will\nbe given a distinguishing version number. The Program (including\nContributions) may always be distributed subject to the version of the\nAgreement under which it was received. In addition, after a new version of\nthe Agreement is published, Contributor may elect to distribute the Program\n(including its Contributions) under the new version. Except as expressly\nstated in Sections 2(a) and 2(b) above, Recipient receives no rights or\nlicenses to the intellectual property of any Contributor under this\nAgreement, whether expressly, by implication, estoppel or otherwise. All\nrights in the Program not expressly granted under this Agreement are\nreserved.\n\nThis Agreement is governed by the laws of the State of New York and the\nintellectual property laws of the United States of America. No party to this\nAgreement will bring a legal action under this Agreement more than one year\nafter the cause of action arose. Each party waives its rights to a jury trial\nin any resulting litigation.\n"
  },
  {
    "path": "examples/redux-counter01/README.md",
    "content": "# systems-toolbox-ui - Redux-style counter example\n\n![screenshot](doc/screenshot.png)\n\n\n## Usage\n\nYou can start the server side application as usual:\n\n    $ lein run\n\nNote that in this step, it does not do anything other than serve a client-side application. This will run the application on **[http://localhost:8888/](http://localhost:8888/)**. You can also change the port using and environment variable:\n\n    $ PORT=9999 lein run\n\nThen, you need to compile the **ClojureScript**:\n\n    $ lein cljsbuild auto release\n\nThis will compile the ClojureScript into JavaScript using `:advanced` optimization.\n\nYou can also use **[lein-figwheel](https://github.com/bhauman/lein-figwheel)** to automatically recompile and reload the application when you code changes:\n\n    $ lein figwheel\n\nTry for example changing the `inc-handler` after creating a few counter with different number. You'll see that each click increments the counter by 1. Then change the function as follows and DON'T reload the page:\n\n```\n(defn inc-handler\n  \"Handler for incrementing specific counter\"\n  [{:keys [current-state msg-payload]}]\n  {:new-state (update-in current-state [:counters (:counter msg-payload)] #(+ % 11))})\n```\n\nFigwheel will reload the application automatically, while the *[systems-toolbox](https://github.com/matthiasn/systems-toolbox)* ensures that each component will retain its previous state. Then click any `inc` button and you'll see that now it increments the counter by 11. This reload also works when changing the CSS.\n\n\n## License\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n"
  },
  {
    "path": "examples/redux-counter01/bower.json",
    "content": "{\n  \"name\": \"trailing-mouse-pointer\",\n  \"version\": \"0.1.0\",\n  \"homepage\": \"https://github.com/matthiasn/systems-toolbox/examples\",\n  \"authors\": [\n    \"Matthias Nehlsen <matthias.nehlsen@gmail.com>\"\n  ],\n  \"description\": \"Tool for visualizing WebSocket Latency.\",\n  \"license\": \"Apache\",\n  \"private\": true,\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"app/bower_components\",\n    \"test\",\n    \"tests\"\n  ],\n  \"dependencies\": {\n    \"pure\": \"~0.5.0\"\n  }\n}\n"
  },
  {
    "path": "examples/redux-counter01/env/dev/cljs/example/dev.cljs",
    "content": "(ns ^:figwheel-no-load example.dev\n  (:require [example.core :as c]\n            [figwheel.client :as figwheel :include-macros true]))\n\n(enable-console-print!)\n\n(defn jscb [] \n  (c/init))\n\n(figwheel/watch-and-reload\n  :websocket-url \"ws://localhost:3452/figwheel-ws\"\n  :jsload-callback jscb)\n\n(c/init)\n"
  },
  {
    "path": "examples/redux-counter01/project.clj",
    "content": "(defproject matthiasn/redux-counter01 \"0.6.1-SNAPSHOT\"\n  :description \"Sample application built with systems-toolbox library\"\n  :url \"https://github.com/matthiasn/systems-toolbox\"\n  :license {:name \"Eclipse Public License\"\n            :url  \"http://www.eclipse.org/legal/epl-v10.html\"}\n  :dependencies [[org.clojure/clojure \"1.10.1\"]\n                 [org.clojure/clojurescript \"1.10.597\"]\n                 [org.clojure/core.async \"0.6.532\"]\n                 [hiccup \"1.0.5\"]\n                 [re-frame \"0.10.9\"]\n                 [clj-pid \"0.1.2\"]\n                 [ch.qos.logback/logback-classic \"1.2.3\"]\n                 [org.clojure/tools.logging \"0.5.0\"]\n                 [matthiasn/systemd-watchdog \"0.1.3\"]\n                 [matthiasn/systems-toolbox \"0.6.41\"]\n                 [matthiasn/systems-toolbox-sente \"0.6.32\"]]\n\n  :source-paths [\"src/clj/\"]\n\n  :clean-targets ^{:protect false} [\"resources/public/js/build/\" \"target/\"]\n\n  :main example.core\n\n  :plugins [[lein-cljsbuild \"1.1.7\"]\n            [lein-figwheel \"0.5.19\"]]\n\n  :figwheel {:server-port 3452\n             :css-dirs    [\"resources/public/css\"]}\n\n  :profiles {:uberjar {:aot        :all\n                       :auto-clean false}}\n\n  :cljsbuild\n  {:builds\n   [{:id           \"dev\"\n     :source-paths [\"src/cljs\" \"env/dev/cljs\"]\n     :figwheel     true\n     :compiler     {:main          \"example.dev\"\n                    :asset-path    \"js/build\"\n                    :optimizations :none\n                    :output-dir    \"resources/public/js/build/\"\n                    :output-to     \"resources/public/js/build/example.js\"\n                    :source-map    true}}\n    {:id           \"release\"\n     :source-paths [\"src/cljs\"]\n     :compiler     {:main          \"example.core\"\n                    :asset-path    \"js/build\"\n                    :output-to     \"resources/public/js/build/example.js\"\n                    :optimizations :advanced}}]})\n"
  },
  {
    "path": "examples/redux-counter01/resources/logback.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Errors -->\n    <appender name=\"ERROR_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- Only ERROR or above should go into this file -->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>ERROR</level>\n        </filter>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- daily rollover -->\n            <fileNamePattern>/tmp/s-t-redux.error.log.%d{yyyy-MM-dd}</fileNamePattern>\n            <!-- keep 30 days' worth of history -->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%d{dd-MM-yy HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} - %msg%n</pattern>\n            <immediateFlush>true</immediateFlush>\n        </encoder>\n    </appender>\n\n    <!-- The rest (messages without markers or with markers other than MESSAGE) -->\n    <appender name=\"UNCATEGORIZED_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>/tmp/s-t-redux.misc.log.%d{yyyy-MM-dd}</fileNamePattern>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%d{dd-MM-yy HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} %class - %msg%n</pattern>\n            <immediateFlush>true</immediateFlush>\n        </encoder>\n    </appender>\n\n    <logger name=\"io.undertow\" level=\"warn\"/>\n    <logger name=\"org.xnio.nio\" level=\"warn\"/>\n    <logger name=\"matthiasn.systems-toolbox\" level=\"info\"/>\n    <logger name=\"iwaswhere-web.store\" level=\"debug\"/>\n    <logger name=\"matthiasn.systems-toolbox-sente\" level=\"info\"/>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n        <appender-ref ref=\"ERROR_FILE\"/>\n        <appender-ref ref=\"UNCATEGORIZED_FILE\"/>\n    </root>\n\n</configuration>"
  },
  {
    "path": "examples/redux-counter01/resources/public/css/example.css",
    "content": ".counters {\n    margin: 20px;\n    font-family: sans-serif;\n    color: #555;\n}\n\n.counters button {\n    font-weight: 300;\n    color: #777;\n}\n\n.counters h1 {\n    font-weight: 300;\n    margin-bottom: 0.3em;\n}\n\n.counters pre {\n    margin-bottom: 2em;\n}"
  },
  {
    "path": "examples/redux-counter01/src/clj/example/core.clj",
    "content": "(ns example.core\n  (:require [matthiasn.systems-toolbox.switchboard :as sb]\n            [matthiasn.systems-toolbox-sente.server :as sente]\n            [example.index :as idx]\n            [clojure.tools.logging :as log]\n            [clj-pid.core :as pid]\n            [matthiasn.systemd-watchdog.core :as wd])\n  (:gen-class))\n\n(defonce switchboard (sb/component :server/switchboard))\n\n(defn restart!\n  \"Starts or restarts system by asking switchboard to fire up the provided\n   ws-cmp, a scheduler component and the ptr component, which handles and counts\n   messages about mouse moves.\"\n  []\n  (sb/send-mult-cmd\n    switchboard\n    [[:cmd/init-comp (sente/cmp-map :server/ws-cmp idx/sente-map)]]))\n\n(defn -main\n  \"Starts the application from command line, saves and logs process ID. The\n   system that is fired up when restart! is called proceeds in core.async's\n   thread pool. Since we don't want the application to exit just because the\n   current thread is out of work after startup, we just put it to sleep.\"\n  [& args]\n  (pid/save \"example.pid\")\n  (pid/delete-on-shutdown! \"example.pid\")\n  (log/info \"Application started, PID\" (pid/current))\n  (restart!)\n  (wd/start-watchdog! 5000)\n  (Thread/sleep Long/MAX_VALUE))\n"
  },
  {
    "path": "examples/redux-counter01/src/clj/example/index.clj",
    "content": "(ns example.index\n  (:require [hiccup.core :refer [html]]))\n\n(defn index-page\n  \"Generates index page HTML.\"\n  [_]\n  (html\n    [:html\n     {:lang \"en\"}\n     [:head\n      [:meta {:name \"viewport\" :content \"width=device-width, minimum-scale=1.0\"}]\n      [:title \"Counter\"]\n      [:link {:href \"/css/example.css\" :rel \"stylesheet\"}]]\n     [:body\n      [:div#counter]\n      [:script {:src \"/js/build/example.js\"}]]]))\n\n(def sente-map\n  \"Configuration map for sente-cmp.\"\n  {:index-page-fn index-page\n   :port          8764\n   :relay-types   #{}})\n"
  },
  {
    "path": "examples/redux-counter01/src/cljs/example/core.cljs",
    "content": "(ns example.core\n  (:require [example.store :as store]\n            [example.counter-ui :as cnt]\n            [matthiasn.systems-toolbox.switchboard :as sb]))\n\n(enable-console-print!)\n\n(defonce switchboard (sb/component :client/switchboard))\n\n(defn init\n  []\n  (sb/send-mult-cmd\n    switchboard\n    [[:cmd/init-comp (cnt/cmp-map :client/cnt-cmp)]\n     [:cmd/init-comp (store/cmp-map :client/store-cmp)]\n     [:cmd/route {:from :client/cnt-cmp :to :client/store-cmp}]\n     [:cmd/observe-state {:from :client/store-cmp :to :client/cnt-cmp}]]))\n\n(init)\n"
  },
  {
    "path": "examples/redux-counter01/src/cljs/example/counter_ui.cljs",
    "content": "(ns example.counter-ui\n  (:require [reagent.core :as r]\n            [re-frame.core :refer [reg-sub subscribe]]\n            [re-frame.db :as rdb]\n            [cljs.pprint :as pp]))\n\n(reg-sub :state (fn [db _] db))\n\n(defn pp-div [current-state]\n  [:pre [:code (with-out-str (pp/pprint current-state))]])\n\n(defn counter-view\n  \"Renders individual counter view, with buttons for increasing or decreasing\n   the value.\"\n  [idx v put-fn]\n  [:div\n   [:h1 v]\n   [:button {:on-click #(put-fn [:cnt/dec {:counter idx}])} \"dec\"]\n   [:button {:on-click #(put-fn [:cnt/inc {:counter idx}])} \"inc\"]])\n\n(defn counters-view\n  \"Renders counters view which observes the state held by the state component.\n  Contains two buttons for adding or removing counters, plus a counter-view\n  for every element in the observed state.\"\n  [put-fn]\n  (let [state (subscribe [:state])]\n    (fn counters-view-render [put-fn]\n      (let [current-state @state\n            indexed (map-indexed vector (:counters current-state))]\n        [:div.counters\n         [pp-div current-state]\n         [:button {:on-click #(put-fn [:cnt/remove])} \"remove\"]\n         [:button {:on-click #(put-fn [:cnt/add])} \"add\"]\n         (for [[idx v] indexed]\n           ^{:key idx} [counter-view idx v put-fn])]))))\n\n(defn state-fn\n  \"Renders main view component and wires the central re-frame app-db as the\n   observed component state, which will then be updated whenever the store-cmp\n   changes.\"\n  [put-fn]\n  (r/render [counters-view put-fn] (.getElementById js/document \"counter\"))\n  {:observed rdb/app-db})\n\n(defn cmp-map\n  [cmp-id]\n  {:cmp-id   cmp-id\n   :state-fn state-fn})\n"
  },
  {
    "path": "examples/redux-counter01/src/cljs/example/store.cljs",
    "content": "(ns example.store\n  \"In this namespace, the app state is managed. One can only interact with the\n   state by sending immutable messages. Each such message is then handled by a\n   handler function. These handler functions here are pure functions, they\n   receive message and previous state and return the new state.\n\n   Both the messages passed around and the new state returned by the handlers\n   are validated using clojure.spec. This eliminates an entire class of possible\n   bugs, where failing to comply with data structure expectations might now\n   immediately become obvious.\"\n  (:require [cljs.spec.alpha :as s]))\n\n(defn inc-handler\n  \"Handler for incrementing specific counter\"\n  [{:keys [current-state msg-payload]}]\n  {:new-state\n   (update-in current-state [:counters (:counter msg-payload)] #(+ % 1))})\n\n(defn dec-handler\n  \"Handler for decrementing specific counter\"\n  [{:keys [current-state msg-payload]}]\n  {:new-state (update-in current-state [:counters (:counter msg-payload)] dec)})\n\n(defn remove-handler\n  \"Handler for removing last counter\"\n  [{:keys [current-state]}]\n  {:new-state (update-in current-state [:counters] #(into [] (butlast %)))})\n\n(defn add-handler\n  \"Handler for adding counter at the end\"\n  [{:keys [current-state]}]\n  {:new-state (update-in current-state [:counters] conj 0)})\n\n(defn state-fn\n  \"Returns clean initial component state atom\"\n  [_put-fn]\n  {:state (atom {:counters [2 0 1]})})\n\n;; validate messages using clojure.spec\n(s/def :redux-ex1/counter #(and (integer? %) (>= % 0)))\n(s/def :cnt/inc (s/keys :req-un [:redux-ex1/counter]))\n(s/def :cnt/dec (s/keys :req-un [:redux-ex1/counter]))\n\n;; validate component state using clojure.spec\n(s/def :redux-ex1/counters (s/coll-of integer?))\n(s/def :redux-ex1/store-spec (s/keys :req-un [:redux-ex1/counters]))\n\n(defn cmp-map\n  [cmp-id]\n  {:cmp-id      cmp-id\n   :state-fn    state-fn\n   :state-spec  :redux-ex1/store-spec\n   :handler-map {:cnt/inc    inc-handler\n                 :cnt/dec    dec-handler\n                 :cnt/remove remove-handler\n                 :cnt/add    add-handler}})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n.idea\n*.iml\n.DS_Store\n\nresources/public/js/build/\nresources/public/bower_components/\n*.pid\n*.out\nresources/public/css/tufte-css/\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/LICENSE",
    "content": "THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC\nLICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM\nCONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.\n\n1. DEFINITIONS\n\n\"Contribution\" means:\n\na) in the case of the initial Contributor, the initial code and\ndocumentation distributed under this Agreement, and\n\nb) in the case of each subsequent Contributor:\n\ni) changes to the Program, and\n\nii) additions to the Program;\n\nwhere such changes and/or additions to the Program originate from and are\ndistributed by that particular Contributor. A Contribution 'originates' from\na Contributor if it was added to the Program by such Contributor itself or\nanyone acting on such Contributor's behalf. Contributions do not include\nadditions to the Program which: (i) are separate modules of software\ndistributed in conjunction with the Program under their own license\nagreement, and (ii) are not derivative works of the Program.\n\n\"Contributor\" means any person or entity that distributes the Program.\n\n\"Licensed Patents\" mean patent claims licensable by a Contributor which are\nnecessarily infringed by the use or sale of its Contribution alone or when\ncombined with the Program.\n\n\"Program\" means the Contributions distributed in accordance with this\nAgreement.\n\n\"Recipient\" means anyone who receives the Program under this Agreement,\nincluding all Contributors.\n\n2. GRANT OF RIGHTS\n\na) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free copyright license to\nreproduce, prepare derivative works of, publicly display, publicly perform,\ndistribute and sublicense the Contribution of such Contributor, if any, and\nsuch derivative works, in source code and object code form.\n\nb) Subject to the terms of this Agreement, each Contributor hereby grants\nRecipient a non-exclusive, worldwide, royalty-free patent license under\nLicensed Patents to make, use, sell, offer to sell, import and otherwise\ntransfer the Contribution of such Contributor, if any, in source code and\nobject code form.  This patent license shall apply to the combination of the\nContribution and the Program if, at the time the Contribution is added by the\nContributor, such addition of the Contribution causes such combination to be\ncovered by the Licensed Patents. The patent license shall not apply to any\nother combinations which include the Contribution. No hardware per se is\nlicensed hereunder.\n\nc) Recipient understands that although each Contributor grants the licenses\nto its Contributions set forth herein, no assurances are provided by any\nContributor that the Program does not infringe the patent or other\nintellectual property rights of any other entity. Each Contributor disclaims\nany liability to Recipient for claims brought by any other entity based on\ninfringement of intellectual property rights or otherwise. As a condition to\nexercising the rights and licenses granted hereunder, each Recipient hereby\nassumes sole responsibility to secure any other intellectual property rights\nneeded, if any. For example, if a third party patent license is required to\nallow Recipient to distribute the Program, it is Recipient's responsibility\nto acquire that license before distributing the Program.\n\nd) Each Contributor represents that to its knowledge it has sufficient\ncopyright rights in its Contribution, if any, to grant the copyright license\nset forth in this Agreement.\n\n3. REQUIREMENTS\n\nA Contributor may choose to distribute the Program in object code form under\nits own license agreement, provided that:\n\na) it complies with the terms and conditions of this Agreement; and\n\nb) its license agreement:\n\ni) effectively disclaims on behalf of all Contributors all warranties and\nconditions, express and implied, including warranties or conditions of title\nand non-infringement, and implied warranties or conditions of merchantability\nand fitness for a particular purpose;\n\nii) effectively excludes on behalf of all Contributors all liability for\ndamages, including direct, indirect, special, incidental and consequential\ndamages, such as lost profits;\n\niii) states that any provisions which differ from this Agreement are offered\nby that Contributor alone and not by any other party; and\n\niv) states that source code for the Program is available from such\nContributor, and informs licensees how to obtain it in a reasonable manner on\nor through a medium customarily used for software exchange.\n\nWhen the Program is made available in source code form:\n\na) it must be made available under this Agreement; and\n\nb) a copy of this Agreement must be included with each copy of the Program.\n\nContributors may not remove or alter any copyright notices contained within\nthe Program.\n\nEach Contributor must identify itself as the originator of its Contribution,\nif any, in a manner that reasonably allows subsequent Recipients to identify\nthe originator of the Contribution.\n\n4. COMMERCIAL DISTRIBUTION\n\nCommercial distributors of software may accept certain responsibilities with\nrespect to end users, business partners and the like. While this license is\nintended to facilitate the commercial use of the Program, the Contributor who\nincludes the Program in a commercial product offering should do so in a\nmanner which does not create potential liability for other Contributors.\nTherefore, if a Contributor includes the Program in a commercial product\noffering, such Contributor (\"Commercial Contributor\") hereby agrees to defend\nand indemnify every other Contributor (\"Indemnified Contributor\") against any\nlosses, damages and costs (collectively \"Losses\") arising from claims,\nlawsuits and other legal actions brought by a third party against the\nIndemnified Contributor to the extent caused by the acts or omissions of such\nCommercial Contributor in connection with its distribution of the Program in\na commercial product offering.  The obligations in this section do not apply\nto any claims or Losses relating to any actual or alleged intellectual\nproperty infringement. In order to qualify, an Indemnified Contributor must:\na) promptly notify the Commercial Contributor in writing of such claim, and\nb) allow the Commercial Contributor tocontrol, and cooperate with the\nCommercial Contributor in, the defense and any related settlement\nnegotiations. The Indemnified Contributor may participate in any such claim\nat its own expense.\n\nFor example, a Contributor might include the Program in a commercial product\noffering, Product X. That Contributor is then a Commercial Contributor. If\nthat Commercial Contributor then makes performance claims, or offers\nwarranties related to Product X, those performance claims and warranties are\nsuch Commercial Contributor's responsibility alone. Under this section, the\nCommercial Contributor would have to defend claims against the other\nContributors related to those performance claims and warranties, and if a\ncourt requires any other Contributor to pay any damages as a result, the\nCommercial Contributor must pay those damages.\n\n5. NO WARRANTY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON\nAN \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER\nEXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR\nCONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A\nPARTICULAR PURPOSE. Each Recipient is solely responsible for determining the\nappropriateness of using and distributing the Program and assumes all risks\nassociated with its exercise of rights under this Agreement , including but\nnot limited to the risks and costs of program errors, compliance with\napplicable laws, damage to or loss of data, programs or equipment, and\nunavailability or interruption of operations.\n\n6. DISCLAIMER OF LIABILITY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY\nCONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION\nLOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE\nEXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY\nOF SUCH DAMAGES.\n\n7. GENERAL\n\nIf any provision of this Agreement is invalid or unenforceable under\napplicable law, it shall not affect the validity or enforceability of the\nremainder of the terms of this Agreement, and without further action by the\nparties hereto, such provision shall be reformed to the minimum extent\nnecessary to make such provision valid and enforceable.\n\nIf Recipient institutes patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Program itself\n(excluding combinations of the Program with other software or hardware)\ninfringes such Recipient's patent(s), then such Recipient's rights granted\nunder Section 2(b) shall terminate as of the date such litigation is filed.\n\nAll Recipient's rights under this Agreement shall terminate if it fails to\ncomply with any of the material terms or conditions of this Agreement and\ndoes not cure such failure in a reasonable period of time after becoming\naware of such noncompliance. If all Recipient's rights under this Agreement\nterminate, Recipient agrees to cease use and distribution of the Program as\nsoon as reasonably practicable. However, Recipient's obligations under this\nAgreement and any licenses granted by Recipient relating to the Program shall\ncontinue and survive.\n\nEveryone is permitted to copy and distribute copies of this Agreement, but in\norder to avoid inconsistency the Agreement is copyrighted and may only be\nmodified in the following manner. The Agreement Steward reserves the right to\npublish new versions (including revisions) of this Agreement from time to\ntime. No one other than the Agreement Steward has the right to modify this\nAgreement. The Eclipse Foundation is the initial Agreement Steward. The\nEclipse Foundation may assign the responsibility to serve as the Agreement\nSteward to a suitable separate entity. Each new version of the Agreement will\nbe given a distinguishing version number. The Program (including\nContributions) may always be distributed subject to the version of the\nAgreement under which it was received. In addition, after a new version of\nthe Agreement is published, Contributor may elect to distribute the Program\n(including its Contributions) under the new version. Except as expressly\nstated in Sections 2(a) and 2(b) above, Recipient receives no rights or\nlicenses to the intellectual property of any Contributor under this\nAgreement, whether expressly, by implication, estoppel or otherwise. All\nrights in the Program not expressly granted under this Agreement are\nreserved.\n\nThis Agreement is governed by the laws of the State of New York and the\nintellectual property laws of the United States of America. No party to this\nAgreement will bring a legal action under this Agreement more than one year\nafter the cause of action arose. Each party waives its rights to a jury trial\nin any resulting litigation.\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/README.md",
    "content": "# systems-toolbox - Trailing Mouse Example\n\n## Usage\n\nBefore the first usage, you want to install the **[Bower](http://bower.io)** dependencies:\n\n    $ cd resources/public/css/\n    $ git clone https://github.com/edwardtufte/tufte-css.git\n\nOnce this is done, you can start the application as usual:\n\n    $ lein run\n\nThis will run the application on **[http://localhost:8888/](http://localhost:8888/)**. However, we will still need to compile the ClojureScript:\n\n    $ lein cljsbuild auto release\n\nThis will compile the ClojureScript into JavaScript using `:advanced` optimization.\n\nYou can also use **[Figwheel](https://github.com/bhauman/lein-figwheel)** to automatically update the application as you make changes. For that, open another terminal:\n\n    $ lein figwheel\n\nYou can then for example inspect the state of a component by using the following commands in the Figwheel REPL:\n\n    (require '[matthiasn.systems-toolbox.switchboard :as sb])\n    (require '[example.core :as c])\n    (sb/send-cmd c/switchboard [:cmd/print-cmp-state :client/histogram-cmp])\n\nBy default, the webserver exposed by the systems-toolbox library listens on port 8888 and only binds to the localhost interface. You can use environment variables to change this behavior, for example:\n\n    $ HOST=\"0.0.0.0\" PORT=8010 lein run\n\n## License\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/env/dev/cljs/example/dev.cljs",
    "content": "(ns ^:figwheel-no-load example.dev\n  (:require [example.core :as c]\n            [figwheel.client :as figwheel :include-macros true]))\n\n(enable-console-print!)\n\n(defn jscb [] \n  (c/init!))\n\n(figwheel/watch-and-reload\n  :websocket-url \"ws://localhost:3451/figwheel-ws\"\n  :jsload-callback jscb)\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/project.clj",
    "content": "(defproject matthiasn/trailing-mouse-pointer \"0.6.1-SNAPSHOT\"\n  :description \"Sample application built with systems-toolbox library\"\n  :url \"https://github.com/matthiasn/systems-toolbox\"\n  :license {:name \"Eclipse Public License\"\n            :url  \"http://www.eclipse.org/legal/epl-v10.html\"}\n  :dependencies [[org.clojure/clojure \"1.10.1\"]\n                 [org.clojure/clojurescript \"1.10.597\"]\n                 [org.clojure/core.async \"0.6.532\"]\n                 [org.clojure/tools.logging \"0.5.0\"]\n                 [org.clojure/tools.namespace \"0.3.1\"]\n                 [ch.qos.logback/logback-classic \"1.2.3\"]\n                 [matthiasn/systemd-watchdog \"0.1.3\"]\n                 [hiccup \"1.0.5\"]\n                 [clj-pid \"0.1.2\"]\n                 [matthiasn/systems-toolbox \"0.6.41\"]\n                 [matthiasn/systems-toolbox-sente \"0.6.32\"]\n                 [matthiasn/systems-toolbox-kafka \"0.6.18\"]\n                 [re-frame \"0.10.9\"]\n                 [clj-time \"0.15.2\"]]\n\n  :source-paths [\"src/cljc/\" \"src/clj/\"]\n\n  :clean-targets ^{:protect false} [\"resources/public/js/build/\" \"target/\"]\n\n  :main example.core\n\n  :plugins [[lein-cljsbuild \"1.1.7\"]\n            [lein-figwheel \"0.5.19\"]]\n\n  :figwheel {:server-port 3451\n             :css-dirs    [\"resources/public/css\"]}\n\n  :profiles {:uberjar {:aot        :all\n                       :auto-clean false}}\n\n  :cljsbuild\n  {:builds\n   [{:id           \"dev\"\n     :source-paths [\"src/cljc/\" \"src/cljs\" \"env/dev/cljs\"]\n     :figwheel     true\n     :compiler     {:main          \"example.dev\"\n                    :asset-path    \"js/build\"\n                    :optimizations :none\n                    :output-dir    \"resources/public/js/build/\"\n                    :output-to     \"resources/public/js/build/example.js\"\n                    :source-map    true}}\n    {:id           \"release\"\n     :source-paths [\"src/cljc/\" \"src/cljs\"]\n     :compiler     {:main          \"example.core\"\n                    :asset-path    \"js/build\"\n                    :output-to     \"resources/public/js/build/example.js\"\n                    :optimizations :advanced}}]})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/resources/logback.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- Errors -->\n    <appender name=\"ERROR_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- Only ERROR or above should go into this file -->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>ERROR</level>\n        </filter>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- daily rollover -->\n            <fileNamePattern>/tmp/st-ex2.error.log.%d{yyyy-MM-dd}</fileNamePattern>\n            <!-- keep 30 days' worth of history -->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%d{dd-MM-yy HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} - %msg%n</pattern>\n            <immediateFlush>true</immediateFlush>\n        </encoder>\n    </appender>\n\n    <!-- The rest (messages without markers or with markers other than MESSAGE) -->\n    <appender name=\"UNCATEGORIZED_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>/tmp/st-ex2.misc.log.%d{yyyy-MM-dd}</fileNamePattern>\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>%d{dd-MM-yy HH:mm:ss.SSS} [%-20.20thread] %-5level %25.25logger{25} %class - %msg%n</pattern>\n            <immediateFlush>true</immediateFlush>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n        <appender-ref ref=\"ERROR_FILE\"/>\n        <appender-ref ref=\"UNCATEGORIZED_FILE\"/>\n    </root>\n\n</configuration>"
  },
  {
    "path": "examples/trailing-mouse-pointer/resources/public/css/example.css",
    "content": "body {\n    margin: 0;\n    -webkit-overflow-scrolling: touch;\n    overflow-x: hidden;\n}\n\n#mouse {\n    position: absolute;\n    top: 0;\n    width: 100%;\n    pointer-events: none;\n    z-index: 10;\n    margin-left: -12.5%;\n}\n\narticle {\n    padding: 2em 0;\n}\n\n#histograms {\n    margin-bottom: 1em;\n}\n\n#histograms div div{\n    display: flex;\n    flex-flow: row;\n    align-items: flex-start;\n}\n\n.show {\n    background-color: wheat;\n    padding: 2px 5px;\n    cursor: pointer;\n}"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/clj/example/core.clj",
    "content": "(ns example.core\n  (:require [example.spec]\n            [matthiasn.systems-toolbox.switchboard :as sb]\n            [matthiasn.systems-toolbox-sente.server :as sente]\n            [matthiasn.systems-toolbox-kafka.kafka-producer2 :as kp2]\n            [example.index :as index]\n            [clojure.tools.logging :as log]\n            [clj-pid.core :as pid]\n            [matthiasn.systemd-watchdog.core :as wd]\n            [example.pointer :as ptr])\n  (:gen-class))\n\n(defonce switchboard (sb/component :server/switchboard))\n\n(defn make-observable [components]\n  (if (System/getenv \"OBSERVER\")\n    (let [cfg {:cfg         {:bootstrap-servers \"localhost:9092\"\n                             :auto-offset-reset \"latest\"\n                             :topic             \"firehose\"}\n               :relay-types #{:firehose/cmp-put\n                              :firehose/cmp-recv}}\n          mapper #(assoc-in % [:opts :msgs-on-firehose] true)\n          components (set (mapv mapper components))\n          firehose-kafka (kp2/cmp-map :server/kafka-firehose cfg)]\n      (conj components firehose-kafka))\n    components))\n\n(defn restart!\n  \"Starts or restarts system by asking switchboard to fire up the provided\n   ws-cmp and the ptr component, which handles and counts messages about mouse\n   moves.\"\n  []\n  (let [components #{(sente/cmp-map :server/ws-cmp index/sente-map)\n                     (ptr/cmp-map :server/ptr-cmp)}\n        components (make-observable components)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp components]\n\n       [:cmd/route {:from :server/ptr-cmp\n                    :to   :server/ws-cmp}]\n\n       [:cmd/route {:from :server/ws-cmp\n                    :to   :server/ptr-cmp}]\n\n       (when (System/getenv \"OBSERVER\")\n         [:cmd/attach-to-firehose :server/kafka-firehose])])))\n\n(defn -main [& args]\n  (pid/save \"example.pid\")\n  (pid/delete-on-shutdown! \"ws-example.pid\")\n  (log/info \"Application started, PID\" (pid/current))\n  (restart!)\n  (wd/start-watchdog! 5000)\n  (Thread/sleep Long/MAX_VALUE))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/clj/example/index.clj",
    "content": "(ns example.index\n  (:require\n    [hiccup.core :refer [html]]))\n\n(defn index-page\n  \"Generates index page HTML with the specified page title.\"\n  [dev?]\n  (html\n    [:html\n     {:lang \"en\"}\n     [:head\n      [:meta {:name \"viewport\" :content \"width=device-width, minimum-scale=1.0\"}]\n      [:title \"Systems-Toolbox: Trailing Mouse Pointer Example\"]\n      [:link {:href \"/css/tufte-css/tufte.css\" :media \"screen\" :rel \"stylesheet\"}]\n      [:link {:href \"/css/example.css\" :media \"screen\" :rel \"stylesheet\"}]\n      [:link {:href \"/images/favicon.png\"\n              :rel  \"shortcut icon\"\n              :type \"image/png\"}]]\n     [:body\n      [:div#ui]\n      [:div#jvm-stats-frame]\n      [:script {:src \"/js/build/example.js\"}]\n      ; Google Analytics for tracking demo page\n      [:script {:async \"\" :src \"https://www.google-analytics.com/analytics.js\"}]\n      [:script (str \"(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n                 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n                    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n                                    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n                 ga('create', 'UA-40261983-6', 'auto'); ga('send', 'pageview');\")]]]))\n\n(def sente-map\n  \"Configuration map for sente-cmp.\"\n  {:index-page-fn index-page\n   :port          8763\n   :relay-types   #{:mouse/pos :mouse/hist}})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljc/example/pointer.cljc",
    "content": "(ns example.pointer\n  \"This component receives messages, keeps a counter, decorates them with the\n   state of the counter, and sends them back. Here, this provides a way to\n   measure roundtrip time from the UI, as timestamps are recorded as the message\n   flows through the system.\n   Also records a recent history of mouse positions for all clients, which the\n   component provides to clients upon request.\")\n\n(defn process-mouse-pos\n  \"Handler function for received mouse positions, increments counter and returns\n   mouse position to sender.\"\n  [{:keys [current-state msg-meta msg-payload]}]\n  (let [new-state (-> current-state\n                      (update-in [:count] inc)\n                      (update-in [:mouse-moves]\n                                 #(vec (take-last 1000 (conj % msg-payload)))))]\n    {:new-state new-state\n     :emit-msg (with-meta\n                 [:mouse/pos (assoc msg-payload :count (:count new-state))]\n                 msg-meta)}))\n\n(defn get-mouse-hist\n  \"Gets the recent mouse position history from server.\"\n  [{:keys [current-state msg-meta]}]\n  {:emit-msg (with-meta [:mouse/hist (:mouse-moves current-state)] msg-meta)})\n\n(defn cmp-map\n  [cmp-id]\n  {:cmp-id      cmp-id\n   :state-fn    (fn [_] {:state (atom {:count 0 :mouse-moves []})})\n   :handler-map {:mouse/pos      process-mouse-pos\n                 :mouse/get-hist get-mouse-hist}\n   :opts        {:msgs-on-firehose      true\n                 :snapshots-on-firehose true}})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljc/example/server_switchboard.cljc",
    "content": "(ns example.server-switchboard\n  (:require\n    [matthiasn.systems-toolbox.switchboard :as sb]\n    [matthiasn.systems-toolbox.scheduler :as sched]\n    [example.pointer :as ptr]))\n\n(defonce switchboard (sb/component :server/switchboard))\n\n(defn restart!\n  \"Starts or restarts system by asking switchboard to fire up the provided\n   ws-cmp, a scheduler component and the ptr component, which handles and counts\n   messages about mouse moves.\"\n  [ws-cmp]\n  (sb/send-mult-cmd\n    switchboard\n    [ws-cmp\n     [:cmd/init-comp (sched/cmp-map :server/scheduler-cmp)]\n     [:cmd/init-comp (ptr/cmp-map :server/ptr-cmp)]\n     [:cmd/route-all {:from :server/ptr-cmp :to :server/ws-cmp}]\n     [:cmd/route {:from :server/ws-cmp :to :server/ptr-cmp}]]))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljc/example/spec.cljc",
    "content": "(ns example.spec\n  (:require\n    #?(:clj  [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])))\n\n(s/def :ex/x integer?)\n(s/def :ex/y integer?)\n\n(s/def :mouse/pos\n  (s/keys :req-un [:ex/x :ex/y]))\n\n(s/def :cmd/show-all #{:local :server})\n\n(s/def :mouse/hist (s/* :mouse/pos))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/core.cljs",
    "content": "(ns example.core\n  (:require [example.spec]\n            [example.store :as store]\n            [example.ui-histograms :as hist]\n            [example.ui-info :as info]\n            [example.re-frame :as ui]\n            [example.observer :as observer]\n            [matthiasn.systems-toolbox.switchboard :as sb]\n            [matthiasn.systems-toolbox-sente.client :as sente]\n            [clojure.string :as s]))\n\n(enable-console-print!)\n\n(defonce switchboard (sb/component :client/switchboard))\n\n(def OBSERVER\n  (or (.-OBSERVER js/window)\n      (s/includes? (aget js/window \"location\" \"search\") \"OBSERVER=true\")))\n\n(defn make-observable [components]\n  (if OBSERVER\n    (let [mapper #(assoc-in % [:opts :msgs-on-firehose] true)]\n      (prn \"Attaching firehose\")\n      (set (mapv mapper components)))\n    components))\n\n(def sente-cfg {:relay-types #{:mouse/pos\n                               :mouse/get-hist\n                               :firehose/cmp-put\n                               :firehose/cmp-recv\n                               :firehose/cmp-publish-state\n                               :firehose/cmp-recv-state}\n                :msgs-on-firehose true})\n\n(defn init! []\n  (let [components #{(sente/cmp-map :client/ws-cmp sente-cfg)\n                     (ui/cmp-map :client/ui-cmp)\n                     (store/cmp-map :client/store-cmp)}\n        components (make-observable components)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp components]\n\n       [:cmd/route {:from :client/ui-cmp\n                    :to   #{:client/store-cmp :client/ws-cmp}}]\n\n       [:cmd/route {:from #{:client/store-cmp\n                            :client/ws-cmp}\n                    :to   :client/store-cmp}]\n\n       [:cmd/observe-state {:from :client/store-cmp\n                            :to   :client/ui-cmp}]\n\n       (when OBSERVER\n         [:cmd/attach-to-firehose :client/ws-cmp])]))\n  (observer/init! switchboard))\n\n(init!)\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/hist_calc.cljs",
    "content": "(ns example.hist-calc)\n\n(defn mean\n  \"From: https://github.com/clojure-cookbook/\"\n  [coll]\n  (let [sum (apply + coll)\n        count (count coll)]\n    (if (pos? count)\n      (/ sum count)\n      0)))\n\n(defn median\n  \"Modified from: https://github.com/clojure-cookbook/\n   Adapted to return nil when collection empty.\"\n  [sorted]\n  (let [cnt (count sorted)\n        halfway (quot cnt 2)]\n    (if (empty? sorted)\n      nil\n      (if (odd? cnt)\n        (nth sorted halfway)\n        (let [bottom (dec halfway)\n              bottom-val (nth sorted bottom)\n              top-val (nth sorted halfway)]\n          (mean [bottom-val top-val]))))))\n\n(defn interquartile-range\n  \"Determines the interquartile range of values in a sequence of numbers.\n   Returns nil when sequence empty or only contains a single entry.\"\n  [sorted]\n  (let [cnt (count sorted)\n        half-cnt (quot cnt 2)\n        q1 (median (take half-cnt sorted))\n        q3 (median (take-last half-cnt sorted))]\n    (when (and q3 q1) (- q3 q1))))\n\n(defn percentile-range\n  \"Returns only the values within the given percentile range.\"\n  [sorted percentile]\n  (let [cnt (count sorted)\n        keep-n (Math/ceil (* cnt (/ percentile 100)))]\n    (take keep-n sorted)))\n\n(defn freedman-diaconis-rule\n  \"Implements approximation of the Freedman-Diaconis rule for determing bin size\n   in histograms: bin size = 2 IQR(x) n^-1/3 where IQR(x) is the interquartile\n   range of the data and n is the number of observations in sample x. Argument\n   is expected to be a sequence of numbers.\"\n  [sample]\n  (let [n (count sample)]\n    (when (pos? n)\n      (* 2 (interquartile-range sample) (Math/pow n (/ -1 3))))))\n\n(defn round-up [n increment] (* (Math/ceil (/ n increment)) increment))\n(defn round-down [n increment] (* (Math/floor (/ n increment)) increment))\n\n(defn best-increment-fn\n  \"Takes a seq of increments, a desired number of intervals in histogram axis,\n   and the range of the values in the histogram. Sorts the values in increments\n   by dividing the range by each to determine number of intervals with this\n   value, subtracting the desired number of intervals, and then returning the\n   increment with the smallest delta.\"\n  [increments desired-n rng]\n  (first (sort-by #(Math/abs (- (/ rng %) desired-n)) increments)))\n\n(defn default-increment-fn\n  \"Determines the increment between intervals in histogram axis.\n   Defaults to increments in a range between 1 and 5,000,000.\"\n  [rng]\n  (if rng\n    (let [multipliers (mapv #(Math/pow 10 %) (range 0 6))\n          increments (flatten (mapv (fn [i] (mapv #(* i %) multipliers)) [1 2.5 5]))\n          best-increment (best-increment-fn increments 5 rng)]\n      (if (zero? (mod best-increment 1))\n        (int best-increment)\n        best-increment))\n    1))\n\n(defn histogram-calc\n  \"Calculations for histogram.\"\n  [{:keys [data bin-cf max-bins increment-fn]}]\n  (let [mx (apply max data)\n        mn (apply min data)\n        rng (- mx mn)\n        increment-fn (or increment-fn default-increment-fn)\n        increment (increment-fn rng)\n        bin-size (max (/ rng max-bins) (* (freedman-diaconis-rule data) bin-cf))\n        binned-freq (frequencies\n                      (mapv (fn [n] (Math/floor (/ (- n mn) bin-size))) data))]\n    {:mn             mn\n     :mn2            (round-down (or mn 0) increment)\n     :mx2            (round-up (or mx 10) increment)\n     :rng            rng\n     :increment      increment\n     :binned-freq    binned-freq\n     :binned-freq-mx (apply max (mapv (fn [[_ f]] f) binned-freq))\n     :bins           (inc (apply max (mapv (fn [[v _]] v) binned-freq)))}))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/histogram.cljs",
    "content": "(ns example.histogram\n  \"Functions for building a histogram, rendered as SVG using Reagent and React.\"\n  (:require [example.hist-calc :as m]))\n\n(def text-default {:stroke \"none\" :fill \"black\" :style {:font-size 12}})\n(def text-bold (merge text-default {:style {:font-weight :bold :font-size 12}}))\n(def x-axis-label (merge text-default {:text-anchor :middle}))\n(def y-axis-label (merge text-default {:text-anchor :end}))\n\n(defn path\n  \"Renders path with the given path description attribute.\"\n  [d]\n  [:path {:fill         :black\n          :stroke       :black\n          :stroke-width 1\n          :d            d}])\n\n(defn histogram-y-axis\n  \"Draws y-axis for histogram.\"\n  [x y h mx y-label]\n  (let [incr (m/default-increment-fn mx)\n        rng (range 0 (inc (m/round-up mx incr)) incr)\n        scale (/ h (dec (count rng)))]\n    [:g\n     [path (str \"M\" x \" \" y \"l 0 \" (* h -1) \" z\")]\n     (for [n rng]\n       ^{:key n}\n       [path (str \"M\" x \" \" (- y (* (/ n incr) scale)) \"l -\" 6 \" 0\")])\n     (for [n rng]\n       ^{:key n}\n       [:text (merge y-axis-label {:x (- x 10)\n                                   :y (- y (* (/ n incr) scale) -4)}) n])\n     [:text (let [x-coord (- x 45)\n                  y-coord (- y (/ h 3))\n                  rotate (str \"rotate(270 \" x-coord \" \" y-coord \")\")]\n              (merge x-axis-label text-bold {:x         x-coord\n                                             :y         y-coord\n                                             :transform rotate})) y-label]]))\n\n(defn histogram-x-axis\n  \"Draws x-axis for histogram.\"\n  [x y mn mx w scale increment x-label]\n  (let [rng (range mn (inc mx) increment)]\n    [:g\n     [path (str \"M\" x \" \" y \"l\" w \" 0 z\")]\n     (for [n rng]\n       ^{:key n}\n       [path (str \"M\" (+ x (* (- n mn) scale)) \" \" y \"l 0 \" 6)])\n     (for [n rng]\n       ^{:key n}\n       [:text (merge x-axis-label {:x (+ x (* (- n mn) scale))\n                                   :y (+ y 20)}) n])\n     [:text (merge x-axis-label text-bold {:x (+ x (/ w 2))\n                                           :y (+ y 48)}) x-label]]))\n\n(defn insufficient-data\n  \"Renders warning when data insufficient.\"\n  [x y w text]\n  [:text {:x           (+ x (/ w 2))\n          :y           (- y 50)\n          :stroke      \"none\"\n          :fill        \"#DDD\"\n          :text-anchor :middle\n          :style       {:font-weight :bold :font-size 24}} text])\n\n(defn histogram-view-fn\n  \"Renders a histogram. Only takes care of the presentational aspects, the\n   calculations are done in the histogram-calc function in\n   matthiasn.systems-toolbox-ui.charts.math.\"\n  [{:keys [x y w h x-label y-label color min-bins warning] :as args}]\n  (let [{:keys [mn mn2 mx2 rng increment bins binned-freq binned-freq-mx]}\n        (m/histogram-calc args)\n        x-scale (/ w (- mx2 mn2))\n        y-scale (/ (- h 20) binned-freq-mx)\n        bar-width (/ (* rng x-scale) bins)]\n    [:g\n     (if (>= bins min-bins)\n       (for [[v f] binned-freq]\n         ^{:key v}\n         [:rect {:x      (+ x (* (- mn mn2) x-scale) (* v bar-width))\n                 :y      (- y (* f y-scale))\n                 :fill   color :stroke \"black\"\n                 :width  bar-width\n                 :height (* f y-scale)}])\n       [insufficient-data x y w warning])\n     [histogram-x-axis x (+ y 7) mn2 mx2 w x-scale increment x-label]\n     [histogram-y-axis (- x 7) y h (or binned-freq-mx 5) y-label]]))\n\n(defn histogram-view\n  \"Renders an individual histogram for the given data, dimension, label and\n   color, with a reasonable size inside a viewBox, which will then scale\n   smoothly into any div you put it in.\"\n  [data label color]\n  [:svg {:width   \"100%\"\n         :viewBox \"0 0 400 250\"}\n   (histogram-view-fn {:data     data\n                       :x        80\n                       :y        180\n                       :w        300\n                       :h        160\n                       :x-label  label\n                       :y-label  \"Frequencies\"\n                       :warning  \"insufficient data\"\n                       :color    color\n                       :bin-cf   0.8\n                       :min-bins 5\n                       :max-bins 25})])\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/observer.cljs",
    "content": "(ns example.observer\n  (:require [reagent.core :as r :refer [atom]]\n            [example.spec]\n            [matthiasn.systems-toolbox.switchboard :as sb]\n            [clojure.set :as s]))\n\n(defn now [] (.getTime (js/Date.)))\n(defn r [] (.random js/Math))\n(defn by-id [id] (.getElementById js/document id))\n\n(def request-animation-frame\n  (or (.-requestAnimationFrame js/window)\n      (.-webkitRequestAnimationFrame js/window)\n      (.-mozRequestAnimationFrame js/window)\n      (.-msRequestAnimationFrame js/window)\n      (fn [callback] (js/setTimeout callback 17))))\n\n(defn nodes-map-fn\n  [nodes-list obs-cfg]\n  (let [nodes (map (fn [k]\n                     (let [fixed-nodes (:fixed-nodes obs-cfg)]\n                       {:name          (if (namespace k)\n                                         (str (namespace k) \"/\" (name k))\n                                         (name k))\n                        :key           k\n                        :x             (if (contains? fixed-nodes k)\n                                         (-> fixed-nodes k :x)\n                                         (+ (* 800 (r)) 100))\n                        :y             (if (contains? fixed-nodes k)\n                                         (-> fixed-nodes k :y)\n                                         (+ (* 800 (r)) 100))\n                        :last-received (now)}))\n                   nodes-list)]\n    (into {} (map (fn [itm] [(:key itm) itm]) nodes))))\n\n(defn links-fn\n  [nodes-map links]\n  (vec (map (fn [m] {:source (:idx ((:from m) nodes-map))\n                     :target (:idx ((:to m) nodes-map))}) links)))\n\n(defn cmp-node\n  [app node cmp-key]\n  (let [x (:x node)\n        y (:y node)\n        grp (:group node)\n        ms-since-rx (- (:now @app) (:last-rx node))\n        ms-since-tx (- (:now @app) (:last-tx node))\n        rx-cnt (:rx-count node)\n        tx-cnt (:tx-count node)]\n    (when x\n      [:g {:transform (str \"translate(\" x \",\" y \")\")\n           :on-click  #(prn ((-> @app\n                                 (:switchboard-state)\n                                 (:components)\n                                 (cmp-key)\n                                 (:state-snapshot-fn))))}\n       [:rect {:x      -60\n               :y      -25\n               :width  120\n               :height 50\n               :rx     5\n               :ry     5\n               :fill   :white\n               :stroke (if (zero? grp) \"#C55\" \"#5C5\") :stroke-width \"2px\"}]\n       [:text {:dy             \"-.5em\"\n               :text-anchor    :middle\n               :text-rendering \"geometricPrecision\"\n               :stroke         :none\n               :fill           :black\n               :font-size      \"11px\"\n               :style          {:font-weight :bold}}\n        (str cmp-key)]\n       [:text {:dy             \"1em\"\n               :text-anchor    :middle\n               :text-rendering \"geometricPrecision\"\n               :stroke         :none\n               :fill           :gray\n               :font-size      \"11px\"\n               :style          {:font-weight :bold}}\n        (str (when rx-cnt (str \"rx: \" rx-cnt)) (when tx-cnt (str \" tx: \" tx-cnt)))]\n       [:rect {:x      44\n               :y      5\n               :width  10\n               :height 10\n               :rx     1\n               :ry     1\n               :fill   :green\n               :style  {:opacity (/ (max 0 (- 250 ms-since-tx)) 250)}}]\n       [:rect {:x      -54\n               :y      5\n               :width  10\n               :height 10\n               :rx     1\n               :ry     1\n               :fill   :orangered\n               :style  {:opacity (/ (max 0 (- 250 ms-since-rx)) 250)}}]])))\n\n(defn system-view\n  \"Renders SVG with an area in which components of a system are shown as a visual representation.\n  These visual representations aim at helping in observing a running system.\"\n  [app put-fn cfg]\n  (let [nodes-map (:nodes-map @app)\n        links (:links @app)]\n    [:div\n     [:svg (merge {:width \"100%\" :viewBox \"0 0 1000 1000\"}\n                  (:svg-props cfg))\n      [:g\n       (for [l links]\n         ^{:key (str \"force-link-\" l)}\n         [:line.link {:stroke       (condp = (:type l)\n                                      :sub \"#00CC33\"\n                                      :tap \"#0033CC\"\n                                      :fh-tap \"#CC0033\")\n                      :stroke-width \"3px\"\n                      :x1           (:x ((:from l) nodes-map))\n                      :x2           (:x ((:to l) nodes-map))\n                      :y1           (:y ((:from l) nodes-map))\n                      :y2           (:y ((:to l) nodes-map))}])\n       (for [[k v] nodes-map]\n         ^{:key (str \"force-node-\" k)}\n         [cmp-node app v k])]]]))\n\n(defn mk-state\n  \"Return clean initial component state atom.\"\n  [obs-cfg]\n  (fn\n    [put-fn]\n    (let [app (atom {:time    (now)\n                     :obs-cfg obs-cfg})\n          system-view-elem (by-id (:dom-id obs-cfg))]\n      (r/render-component [system-view app put-fn obs-cfg] system-view-elem)\n      (letfn [(step []\n                (request-animation-frame step)\n                (swap! app assoc :now (now)))]\n        (request-animation-frame step))\n      {:state app})))\n\n(defn count-msg\n  \"Creates a handler function for collecting stats about messages and and their display.\"\n  [ts-key count-key]\n  (fn [{:keys [cmp-state msg-payload cmp-id]}]\n    (let [other-id (:cmp-id msg-payload)]\n      (when (:switchboard-state @cmp-state)\n        (swap! cmp-state assoc-in [:nodes-map other-id ts-key] (now))\n        (swap! cmp-state update-in [:nodes-map other-id count-key] #(inc (or % 0)))\n        (swap! cmp-state assoc-in [:nodes-map cmp-id :last-rx] (now))\n        (swap! cmp-state update-in [:nodes-map cmp-id :rx-count] #(inc (or % 0)))))\n    {}))\n\n(defn state-snapshot-handler\n  \"Creates a handler function for component snapshot messages. Uses messages from switchboard for\n  configuring the UI.\"\n  [switchbrd-id]\n  (fn [{:keys [cmp-state msg-payload] :as msg-map}]\n    (let [other-id (:cmp-id msg-payload)\n          count-fn (count-msg :last-rx :rx-count)]\n      (count-fn msg-map)\n      (when (= other-id switchbrd-id)\n        (let [switchboard-state (:snapshot msg-payload)\n              obs-cfg (:obs-cfg @cmp-state)\n              nodes-map (nodes-map-fn (keys (:components switchboard-state)) obs-cfg)\n              subscriptions-set (:subs switchboard-state)\n              taps-set (:taps switchboard-state)\n              fh-taps-set (:fh-taps switchboard-state)\n              links (s/union subscriptions-set taps-set fh-taps-set)]\n          (swap! cmp-state assoc :switchboard-state switchboard-state)\n          (swap! cmp-state assoc :nodes-map nodes-map)\n          (swap! cmp-state assoc :links links))))\n    {}))\n\n(defn cmp-map\n  {:added \"0.3.1\"}\n  [cmp-id obs-cfg]\n  {:cmp-id      cmp-id\n   :state-fn    (mk-state obs-cfg)\n   :handler-map {:firehose/cmp-put           (count-msg :last-tx :tx-count)\n                 :firehose/cmp-publish-state (state-snapshot-handler (:switchbrd-id obs-cfg))\n                 :firehose/cmp-recv          (count-msg :last-rx :rx-count)\n                 :firehose/cmp-recv-state    (count-msg :last-rx :rx-count)}\n   :opts        {:snapshots-on-firehose false\n                 :reload-cmp            false}})\n\n(def observer-cfg\n  {:dom-id \"observer\"\n   :switchbrd-id :client/switchboard\n   :fixed-nodes   {:client/ws-cmp           {:x 100 :y 300}\n                   :client/observer-cmp     {:x 360 :y 550}\n                   :client/switchboard      {:x 360 :y 420}\n                   :client/ui-cmp           {:x 650 :y 300}\n                   :client/store-cmp        {:x 360 :y 120}}\n   :svg-props     {:viewBox \"0 0 1000 600\"}})\n\n(defn init!\n  \"Initialize and wire Observer component.\"\n  [switchboard]\n  (sb/send-mult-cmd\n    switchboard\n    [[:cmd/init-comp (cmp-map :client/observer-cmp observer-cfg)]\n     [:cmd/attach-to-firehose :client/observer-cmp]]))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/re_frame.cljs",
    "content": "(ns example.re-frame\n  (:require-macros [reagent.ratom :refer [reaction]])\n  (:require [reagent.core :as reagent]\n            [example.ui-mouse-moves :as mm]\n            [example.ui-histograms :as hist]\n            [example.ui-info :as info]\n            [example.utils :as u]\n            [re-frame.core :refer [reg-sub]]\n            [re-frame.db :as rdb]))\n\n;; Subscription Handlers\n(reg-sub :local (fn [db _] (:local db)))\n(reg-sub :rtt-times (fn [db _] (:rtt-times db)))\n(reg-sub :network-times (fn [db _] (:network-times db)))\n(reg-sub :from-server (fn [db _] (:from-server db)))\n\n(defn re-frame-ui\n  \"Main view component\"\n  [put-fn]\n  [:div\n   [:div#mouse\n    [mm/mouse-view]]\n   [:article\n    [:h1 \"WebSockets Latency Visualization\"]\n    [:p [:a {:href \"https://github.com/matthiasn\" :target \"_blank\"}\n         \"Matthias Nehlsen\"]]\n    [:section\n     [:p\n      \"WebSockets bring bi-directional communication to the browser. This\n       enables you to deliver interactive, real time web applications where\n       all the data is as of right now, rather than always being outdated,\n       and then constantly refreshed.\"]\n     [:p\n      \"But how fast is this transport mechanism? Let's have a look. You may\n      have noticed the circle around the mouse pointer on this page, \"\n      [:label {:for \"explain1\" :class \"margin-toggle\"} \" ⊕ \"]\n      [:input#explain1 {:type \"checkbox\" :class \"margin-toggle\"}]\n      [:span.marginnote\n       \"What happens here is that movements of the mouse (or your finger on\n        your mobile device) are captured. The more reddish one is then painted\n        immediately, whereas the bluish one is painted after the event is sent\n        to a server somewhere in Germany, and then back to wherever you are.\"]\n      \"or in fact the two circles, where one of them appears to follow the other.\n       Both represent your last mouse position, only that one was sent to and\n       returned from the server in the meantime. This gives you an intuition\n       for how long it takes. Also, with your movement of the mouse, you\n       generate data for the histograms below, which show the roundtrip duration:\"]\n\n     [hist/histograms-view]\n     [info/info-view put-fn]\n\n     [:p\n      \"Now, since we are already capturing the movement of the mouse, you\n       may think that it could be interesting to see where the users' mouses\n       go, as a proxy for where they are looking on a page. Surely not as\n       accurate as actual eye tracking, but probably much better than nothing.\n       Now let's see where your mouse was since you started interacting with\n       this page. Click the \\\"show all\\\" button in the info section below,\"\n      [:label {:for \"explain2\" :class \"margin-toggle\"} \" ⊕ \"]\n      [:input#explain2 {:type \"checkbox\" :class \"margin-toggle\"}]\n      [:span.marginnote\n       \"By clicking those buttons again, you can switch the display on or off.\"]\n      \" and you see where your mouse goes. Then, by clicking\n       \\\"show all (server)\\\", you can also display the most recent mouse\n       positions of all visitors on this page.\"]\n     [:p\n      \"You are looking at a web application written in Clojure andClojureScript.\n       It is one of the example applications of the systems-toolbox library.\n       The histograms above are rendered entirely in ClojureScript\n       - without any additional charting library.\"\n      [:span.marginnote \"The \"\n       [:a {:href \"http://en.wikipedia.org/wiki/Freedman–Diaconis_rule\"}\n        \"Freedman-Diaconis rule\"]\n       \" determines the number of bins in the histograms. The first\n        histogram takes the entire sample into account whereas the second only\n        displays the observations that fall within the 99th percentile to\n        remove potential outliers.\"]]\n     [:figure\n      [:label {:for \"fig2\" :class \"margin-toggle\"} \" ⊕ \"]\n      [:input#fig2 {:type \"checkbox\" :class \"margin-toggle\"}]\n      [:span.marginnote\n       \"Structure of the ClojureScript application, with their message flow\n        visualized as rx and tx LEDs, like on a network card.\"]\n      [:div#observer]]\n     [:p\n      \"If you want to know how this application was built, have a look at the\n       code on \"\n      [:a {:href   \"https://github.com/matthiasn/systems-toolbox\"\n           :target \"_blank\"} \"GitHub\"]\n      \" or the book \"\n      [:a {:href   \"https://leanpub.com/building-a-system-in-clojure\"\n           :target \"_blank\"} \"Building Systems in Clojure(Script)\"]\n      \". Also, check for a future blog post on \"\n      [:a {:href \"https://matthiasnehlsen.com\" :target \"_blank\"}\n       \"matthiasnehlsen.com\"]\n      \".\"]\n     [:p\n      \"Finally, if you like the layout of this page, you need to look at \"\n      [:a {:href \"https://edwardtufte.github.io/tufte-css/\" :target \"_blank\"}\n       \"Tufte CSS\"]\n      \". It allowed me to write this application with only around 30 lines of\n       CSS, most of which is related to the flexbox layout for histogram SVGs.\"]]]])\n\n(defn state-fn\n  \"Renders main view component and wires the central re-frame app-db as the\n   observed component state, which will then be updated whenever the store-cmp\n   changes.\"\n  [put-fn]\n  (reagent/render [re-frame-ui put-fn] (.getElementById js/document \"ui\"))\n  (aset js/window \"onmousemove\"\n        (fn [ev]\n          (put-fn [:mouse/pos {:x (.-pageX ev) :y (.-pageY ev)}])))\n  (aset js/window \"ontouchmove\"\n        (fn [ev]\n          (let [t (aget (.-targetTouches ev) 0)]\n            (put-fn [:mouse/pos {:x (.-pageX t) :y (.-pageY t)}]))))\n  {:observed rdb/app-db})\n\n(defn cmp-map\n  [cmp-id]\n  {:cmp-id   cmp-id\n   :state-fn state-fn\n   :opts     {:msgs-on-firehose true}})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/store.cljs",
    "content": "(ns example.store)\n\n(defn mouse-pos-handler\n  \"Handler function for mouse position messages. When message from server:\n    - determine the round trip time (RTT) by subtracting the message creation\n      timestamp from the timestamp when the message is finally received by the\n      store component.\n    - determine server side processing time is determined. For this, we can use\n      the timestamps from when the ws-cmp on the server side emits a message\n      coming from the client and when the processed message is received back for\n      delivery to the client.\n    - update component state with the new mouse location under :from-server.\n   When message received locally, only update position in :local.\"\n  [{:keys [current-state msg-payload msg-meta]}]\n  (let [new-state\n        (if (:count msg-payload)\n          (let [mouse-out-ts (:out-ts (:client/ui-cmp msg-meta))\n                store-in-ts (:in-ts (:client/store-cmp msg-meta))\n                rt-time (- store-in-ts mouse-out-ts)\n                srv-ws-meta (:server/ws-cmp msg-meta)\n                srv-proc-time (- (:in-ts srv-ws-meta) (:out-ts srv-ws-meta))\n                network-time (- rt-time srv-proc-time)]\n            (-> current-state\n                (assoc-in [:from-server] (assoc msg-payload :rt-time rt-time))\n                (update-in [:count] inc)\n                (update-in [:rtt-times] conj rt-time)\n                ;(update-in [:rtt-times] #(sort (conj % rt-time)))\n                (update-in [:network-times] conj network-time)))\n          (-> current-state\n              (assoc-in [:local] msg-payload)\n              (update-in [:local-hist] conj msg-payload)))]\n    {:new-state new-state}))\n\n(defn show-all-handler\n  \"Toggles boolean value in component state for provided key.\"\n  [{:keys [current-state msg-payload]}]\n  {:new-state (update-in current-state [:show-all msg-payload] not)})\n\n(defn mouse-hist-handler\n  \"Saves the received vector with mouse positions in component state.\"\n  [{:keys [current-state msg-payload]}]\n  {:new-state (assoc-in current-state [:server-hist] msg-payload)})\n\n(defn state-fn\n  \"Return clean initial component state atom.\"\n  [_put-fn]\n  {:state (atom {:count         0\n                 :rtt-times     []\n                 :network-times []\n                 :local         {:x 0 :y 0}\n                 :show-all      {:local  false\n                                 :remote false}})})\n\n(defn cmp-map\n  \"Configuration map that specifies how to instantiate component.\"\n  [cmp-id]\n  {:cmp-id      cmp-id\n   :state-fn    state-fn\n   :handler-map {:mouse/pos    mouse-pos-handler\n                 :cmd/show-all show-all-handler\n                 :mouse/hist   mouse-hist-handler}\n   :opts        {:msgs-on-firehose true}})\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/ui_histograms.cljs",
    "content": "(ns example.ui-histograms\n  (:require-macros [reagent.ratom :refer [reaction]])\n  (:require [example.histogram :as h]\n            [matthiasn.systems-toolbox.component :as st]\n            [re-frame.core :refer [subscribe]]\n            [example.hist-calc :as m]\n            [reagent.core :as r]\n            [example.utils :as u]))\n\n(defn histograms-view\n  \"Renders histograms with different data sets, labels and colors.\"\n  []\n  (let [rtt-times (subscribe [:rtt-times])\n        network-times (subscribe [:network-times])\n        show-all? (r/atom false)]\n    (fn histograms-render []\n      (let [rtt-times (sort @rtt-times)]\n        [:figure#histograms.fullwidth\n         [:span.show\n          {:on-click #(swap! show-all? not)}\n          (if @show-all?\n            \"show single\"\n            \"show all\")]\n         (if @show-all?\n           [:div\n            [:div\n             [h/histogram-view\n              rtt-times\n              \"Roundtrip t/ms\" \"#D94B61\"]\n             [h/histogram-view\n              (m/percentile-range rtt-times 99)\n              \"Roundtrip t/ms (within 99th percentile)\" \"#D94B61\"]\n             [h/histogram-view\n              (m/percentile-range rtt-times 95)\n              \"Roundtrip t/ms (within 95th percentile)\" \"#D94B61\"]]\n            #_\n            (let [network-times (sort @network-times)]\n              [:div\n               [h/histogram-view\n                network-times\n                \"Network t/ms\" \"#66A9A5\"]\n               [h/histogram-view\n                (m/percentile-range network-times 99)\n                \"Network t/ms (within 99th percentile)\" \"#66A9A5\"]\n               [h/histogram-view\n                (m/percentile-range network-times 95)\n                \"Network t/ms (within 95th percentile)\" \"#66A9A5\"]])]\n           [:div\n            [:div\n             [h/histogram-view\n              rtt-times\n              \"Roundtrip t/ms\" \"#D94B61\"]]])]))))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/ui_info.cljs",
    "content": "(ns example.ui-info\n  (:require [re-frame.core :refer [subscribe]]))\n\n(defn info-view\n  \"Show some info about app state, plus toggle buttons for showing all mouse\n   positions, both local and from server.\"\n  [put-fn]\n  (let [from-server (subscribe [:from-server])\n        rtt-times (subscribe [:rtt-times])\n        local (subscribe [:local])]\n    (fn [put-fn]\n      (let [last-rt (:rt-time @from-server)\n            rtt-times @rtt-times\n            mx (apply max rtt-times)\n            mn (apply min rtt-times)\n            cnt (count rtt-times)\n            mean (.round js/Math (if (seq rtt-times)\n                                   (/ (apply + rtt-times) cnt)\n                                   0))\n            local-pos @local\n            latency-string (str mean \" mean / \" mn \" min / \" mx \" max / \" last-rt\n                                \" last\")]\n        [:div\n         [:strong \"Mouse Moves Processed: \"] cnt [:br]\n         [:strong \"Processed since Startup: \"]\n         (:count @from-server) [:br]\n         [:strong \"Current position: \"] \"x: \" (:x local-pos) \" y: \" (:y local-pos)\n         [:br]\n         [:strong \"Latency (ms): \"] latency-string [:br]\n         [:br]\n         #_#_[:button {:on-click #(put-fn [:cmd/show-all :local])} \"show all\"]\n             [:button {:on-click #(do (put-fn [:mouse/get-hist])\n                                      (put-fn [:cmd/show-all :server]))}\n              \"show all (server)\"]]))))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/ui_mouse_moves.cljs",
    "content": "(ns example.ui-mouse-moves\n  (:require [re-frame.core :refer [subscribe]]\n            [reagent.core :as rc]))\n\n;; some SVG defaults\n(def circle-defaults {:fill \"rgba(255,0,0,0.1)\n\" :stroke \"rgba(0,0,0,0.5)\"\n                      :stroke-width 2 :r 15})\n(def text-default {:stroke \"none\" :fill \"black\" :style {:font-size 12}})\n(def text-bold (merge text-default {:style {:font-weight :bold :font-size 12}}))\n\n(defn mouse-hist-view\n  \"Render SVG group with filled circles from a vector of mouse positions in state.\"\n  [state state-key stroke fill]\n  (let [positions (map-indexed vector (state-key state))]\n    (when (seq positions)\n      [:g {:opacity 0.5}\n       (for [[idx pos] positions]\n         ^{:key (str \"circle\" state-key idx)}\n         [:circle {:stroke       stroke\n                   :stroke-width 2\n                   :r            15\n                   :cx           (:x pos)\n                   :cy           (:y pos)\n                   :fill         fill}])])))\n\n(defn trailing-circles\n  \"Displays two transparent circles. The position of the circles comes from\n   the most recent messages, one sent locally and the other with a roundtrip to\n   the server in between. This makes it easier to visually detect any delays.\"\n  []\n  (let [local-pos (subscribe [:local])\n        from-server (subscribe [:from-server])]\n    (fn []\n      [:g\n       [:circle (merge circle-defaults {:cx (:x @local-pos)\n                                        :cy (:y @local-pos)})]\n       [:circle (merge circle-defaults {:cx   (:x @from-server)\n                                        :cy   (:y @from-server)\n                                        :fill \"rgba(0,0,255,0.1)\"})]])))\n\n(defn mouse-view\n  \"Renders SVG with both local mouse position and the last one returned from the\n   server, in an area that covers the entire visible page.\"\n  []\n  (let [local (rc/atom {})\n        update-dim (fn [_ev]\n                     (let [h 3000\n                           w (.-innerWidth js/window)]\n                       (swap! local assoc :width w)\n                       (swap! local assoc :height h)))]\n    (update-dim nil)\n    (aset js/window \"onresize\" update-dim)\n    (fn mouse-view-render []\n      [:div\n       [:svg {:width  (:width @local)\n              :height (:height @local)}\n        [trailing-circles]\n        #_#_(when (-> state-snapshot :show-all :local)\n              [mouse-hist-view state-snapshot :local-hist\n               \"rgba(0,0,0,0.06)\" \"rgba(0,255,0,0.05)\"])\n            (when (-> state-snapshot :show-all :server)\n              [mouse-hist-view state-snapshot :server-hist\n               \"rgba(0,0,0,0.06)\" \"rgba(0,0,128,0.05)\"])]])))\n"
  },
  {
    "path": "examples/trailing-mouse-pointer/src/cljs/example/utils.cljs",
    "content": "(ns example.utils\n  (:require-macros [cljs.core.async.macros :refer [go-loop]])\n  (:require [cljs.core.async :refer [chan sliding-buffer timeout put! <!]]))\n\n(defn throttle [f time]\n  (let [c (chan (sliding-buffer 1))]\n    (go-loop []\n             (apply f (<! c))\n             (<! (timeout time))\n             (recur))\n    (fn [& args]\n      (put! c (or args [])))))"
  },
  {
    "path": "perf/matthiasn/systems_toolbox/runtime_perf_test.cljc",
    "content": "(ns matthiasn.systems-toolbox.runtime-perf-test\n\n  \"This namespace provides a sanity check before endeavoring in premature optimization. For example,\n  if we can swap or reset an atom 70 million times per second on the JVM but 'only' process 80K messages\n  per second, it is quite unlikely that using volatile! instead of an atom would speed things up. Here,\n  core.async seems to be the more interesting candidate to look at, where the very simple operation\n  of putting a message on a chan with an attached mult and no other chan to take it off there can be\n  performed a little over 200K times per second.\"\n\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go go-loop]]))\n  (:require\n    #?(:clj  [clojure.test :refer [deftest testing is]]\n       :cljs [cljs.test :refer-macros [deftest testing is]])\n    #?(:clj  [clojure.core.async :refer [<! chan mult buffer put! go go-loop timeout promise-chan >! tap\n                                         sliding-buffer onto-chan]]\n       :cljs [cljs.core.async :refer [<! chan mult put! timeout promise-chan >! tap sliding-buffer]])\n             [matthiasn.systems-toolbox.test-promise :as tp]\n             [matthiasn.systems-toolbox.component :as component]\n    #?(:clj  [clojure.tools.logging :as log]\n       :cljs [matthiasn.systems-toolbox.log :as log])))\n\n; here, we can tweak the number of test runs, which is useful if we are interested in JIT optimizations\n(def test-runs 0)\n\n(defn swap-atom-repeatedly-fn\n  []\n  \"This test aims at getting some perspective how expensive swapping an atom is in Clojure/ClojureScript.\n  Answer: not terribly expensive. On the JVM, this can be performed around 70 million times per second,\n  whereas in ClojureScript, this can be done 15 million times per second (2015 Retina MacBook).\"\n  (let [start-ts (component/now)\n        cnt (* 1000 1000)\n        state (atom 0)]\n    (dotimes [_ cnt] (swap! state inc))\n    (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n      (log/info \"Atom swaps/s:\" ops-per-sec)\n      (is (> ops-per-sec 1000)))))\n\n(deftest swap-atom-repeatedly\n  (dotimes [_ test-runs]\n    (swap-atom-repeatedly-fn)))\n\n(defn swap-watched-atom-repeatedly-fn\n  []\n  \"This test aims at getting some perspective how expensive swapping an atom is in Clojure/ClojureScript.\n  Answer: not terribly expensive. On the JVM, this can be performed around 70 million times per second,\n  whereas in ClojureScript, this can be done 15 million times per second (2015 Retina MacBook).\"\n  (let [start-ts (component/now)\n        cnt (* 1000 1000)\n        state (atom 0)]\n    (add-watch state :watcher (fn [_ _ _ _new-state] #()))\n    (dotimes [_ cnt] (swap! state inc))\n    (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n      (log/info \"Watched atom swaps/s:\" ops-per-sec)\n      (is (> ops-per-sec 1000)))))\n\n(deftest swap-watched-atom-repeatedly\n  (dotimes [_ test-runs]\n    (swap-watched-atom-repeatedly-fn)))\n\n(defn reset-atom-repeatedly-fn\n  []\n  \"This test aims at getting some perspective how expensive resetting an atom is in Clojure/ClojureScript.\n  Answer: not terribly expensive. On the JVM, this can be performed around 90 million times per second,\n  whereas in ClojureScript, this can be done 15 million times per second on PhantomJS and over 60 million\n  times per second in Firefox (2015 Retina MacBook).\"\n  (let [start-ts (component/now)\n        cnt (* 1000 1000)\n        state (atom 0)]\n    (dotimes [n cnt] (reset! state n))\n    (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n      (log/info \"Atom resets/s:\" ops-per-sec)\n      (is (> ops-per-sec 1000)))))\n\n(deftest reset-atom-repeatedly\n  (dotimes [_ test-runs]\n    (reset-atom-repeatedly-fn)))\n\n(defn deref-atom-repeatedly-fn\n  []\n  \"This test aims at getting some perspective how expensive dereferencing an atom is in Clojure/ClojureScript.\n  Answer: not terribly expensive. On the JVM, this can be performed around 30 million times per second,\n  whereas in ClojureScript, this can be done 8 million times per second (2015 Retina MacBook).\"\n  (let [start-ts (component/now)\n        cnt (* 1000 1000)\n        state (atom {:foo 1000})]\n    (dotimes [n cnt] (/ (:foo @state) 10))\n    (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n      (log/info \"Atom derefs/s:\" ops-per-sec)\n      (is (> ops-per-sec 1000)))))\n\n(deftest deref-atom-repeatedly\n  (dotimes [_ test-runs]\n    (deref-atom-repeatedly-fn)))\n\n(defn put-on-chan-repeatedly-fn\n  \"Channel with attached mult and no other channels tapping into mult: messages silently dropped.\"\n  []\n  (let [start-ts (component/now)\n        cnt (* 100 1000)\n        ch (chan)\n        m (mult ch)\n        done (promise-chan)]\n    (go\n      (dotimes [n cnt] (>! ch n))\n      (put! done true))\n\n    (tp/w-timeout cnt (go\n                        (testing \"all messages received\"\n                          (is (true? (<! done))))\n                        (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n                          (log/info \"Channel puts/s:\" ops-per-sec)\n                          (is (> ops-per-sec 1000)))))))\n\n\n(deftest put-on-chan-repeatedly1 (put-on-chan-repeatedly-fn))\n(deftest put-on-chan-repeatedly2 (put-on-chan-repeatedly-fn))\n(deftest put-on-chan-repeatedly3 (put-on-chan-repeatedly-fn))\n(deftest put-on-chan-repeatedly4 (put-on-chan-repeatedly-fn))\n(deftest put-on-chan-repeatedly5 (put-on-chan-repeatedly-fn))\n(deftest put-on-chan-repeatedly6 (put-on-chan-repeatedly-fn))\n\n(deftest put-consume-repeatedly\n  \"Channel with attached go-loop, simple calculation using messages from channel.\"\n  (let [start-ts (component/now)\n        cnt (* 100 1000)\n        ch (chan)\n        state (atom 0)\n        done (promise-chan)]\n    (go (dotimes [n cnt] (>! ch n)))\n\n    (go-loop []\n      (let [n (<! ch)\n            res (+ @state n)]\n        (reset! state res)\n        (when (= (dec cnt) n)\n          (put! done true)))\n      (recur))\n\n    (tp/w-timeout cnt (go\n                        (testing \"all messages received\"\n                          (is (true? (<! done))))\n                        (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n                          (log/info \"Channel puts and consume/s:\" ops-per-sec)\n                          (is (> ops-per-sec 1000)))\n                        (testing \"all messages received (sum of all number sent matches)\"\n                          (is (= @state (reduce + (range cnt)))))))))\n\n(deftest put-consume-mult-repeatedly\n  \"Channel with attached go-loop, simple calculation using messages from channel.\"\n  (let [start-ts (component/now)\n        cnt (* 100 1000)\n        ch (chan)\n        m (mult ch)\n        ch2 (chan)\n        state (atom 0)\n        done (promise-chan)]\n    (go (dotimes [n cnt] (>! ch n)))\n\n    (tap m ch2)\n\n    (go-loop []\n      (let [n (<! ch2)\n            res (+ @state n)]\n        (reset! state res)\n        (when (= (dec cnt) n)\n          (put! done true)))\n      (recur))\n\n    (tp/w-timeout cnt (go\n                        (testing \"all messages received\"\n                          (is (true? (<! done))))\n                        (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n                          (log/info \"Channel puts and consume from mult/s:\" ops-per-sec)\n                          (is (> ops-per-sec 1000)))\n                        (testing \"all messages received (sum of all number sent matches)\"\n                          (is (= @state (reduce + (range cnt)))))))))\n\n(defn put-consume-mult-w-pub-repeatedly-fn\n  \"Channel with attached go-loop, simple calculation using messages from channel, publication of state change. This\n  imitates the basic use case of the systems-toolbox: there's a go-loop, some processing and publication of component\n  state. Running this test gives some perspective of the amount of overhead that the systems-toolbox introduces,\n  such as adding metadata to messages.\"\n  []\n  (let [start-ts (component/now)\n        cnt (* 100 1000)\n        ch (chan)\n        m (mult ch)\n        ch2 (chan)\n        state-pub-chan (chan (sliding-buffer 1))\n        state-mult (mult state-pub-chan)\n        state (atom 0)\n        done (promise-chan)]\n\n    (go-loop []\n      (let [n (<! ch2)\n            res (+ @state n)]\n        (reset! state res)\n        (>! state-pub-chan res)\n        (when (= (dec cnt) n)\n          (put! done true)))\n      (recur))\n\n    (tap m ch2)\n    (go (dotimes [n cnt] (>! ch n)))\n\n    (tp/w-timeout cnt (go\n                        (testing \"promise delivered\"\n                          (is (true? (<! done))))\n                        (let [ops-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n                          (log/info \"Channel puts and consume from mult/s (w/pub):\" ops-per-sec)\n                          (is (> ops-per-sec 1000)))\n                        (testing \"all messages received (sum of all number sent matches)\"\n                          (is (= @state (reduce + (range cnt)))))\n                        :done))))\n\n(deftest put-consume-mult-w-pub-repeatedly (put-consume-mult-w-pub-repeatedly-fn))\n(deftest put-consume-mult-w-pub-repeatedly2 (put-consume-mult-w-pub-repeatedly-fn))\n(deftest put-consume-mult-w-pub-repeatedly3 (put-consume-mult-w-pub-repeatedly-fn))\n(deftest put-consume-mult-w-pub-repeatedly4 (put-consume-mult-w-pub-repeatedly-fn))\n(deftest put-consume-mult-w-pub-repeatedly5 (put-consume-mult-w-pub-repeatedly-fn))\n(deftest put-consume-mult-w-pub-repeatedly6 (put-consume-mult-w-pub-repeatedly-fn))\n"
  },
  {
    "path": "project.clj",
    "content": "(defproject matthiasn/systems-toolbox \"0.6.41\"\n  :description \"Toolbox for building Systems in Clojure\"\n  :url \"https://github.com/matthiasn/systems-toolbox\"\n  :license {:name \"Eclipse Public License\"\n            :url  \"http://www.eclipse.org/legal/epl-v10.html\"}\n\n  :source-paths [\"src/cljc\"]\n\n  :dependencies [[org.clojure/core.match \"0.3.0\"]\n                 [org.clojure/tools.logging \"0.5.0\"]\n                 [io.aviso/pretty \"0.1.37\"]\n                 [expound \"0.8.2\"]\n                 [com.lucasbradstreet/cljs-uuid-utils \"1.0.2\"]]\n\n  :plugins [[lein-codox \"0.10.7\"]\n            [test2junit \"1.4.2\"]\n            [lein-doo \"0.1.11\"]\n            [lein-cloverage \"1.1.2\"]\n            [lein-ancient \"0.6.15\"]\n            [com.jakemccrary/lein-test-refresh \"0.24.1\"]\n            [lein-cljsbuild \"1.1.7\"]]\n\n  :test2junit-output-dir\n  ~(or (System/getenv \"CIRCLE_TEST_REPORTS\") \"target/test2junit\")\n\n  :test-refresh {:notify-on-success false\n                 :changes-only      false}\n\n  :clean-targets ^{:protect false} [\"target/\" \"out/\"]\n\n  :test-paths [\"test\"]\n  ;:test-paths [\"dev-resources\" \"test\" \"perf\"]\n\n  :profiles {:dev {:dependencies [[org.clojure/clojure \"1.10.1\"]\n                                  [org.clojure/clojurescript \"1.10.597\"]\n                                  [org.clojure/core.async \"0.6.532\"]\n                                  [ch.qos.logback/logback-classic \"1.2.3\"]]\n                   :jvm-opts     [\"-Dclojure.compiler.direct-linking=true\"]}}\n\n  :cljsbuild\n  {:builds [{:id           \"cljs-test\"\n             :source-paths [\"src\" \"test\"]\n             :compiler     {:output-to     \"out/testable.js\"\n                            :main          matthiasn.systems-toolbox.runner\n                            :target        :nodejs\n                            :optimizations :advanced}}\n            {:id           \"cljs-perf-test\"\n             :source-paths [\"perf\" \"src\" \"test\"]\n             :compiler     {:output-to     \"out/perf.js\"\n                            :target        :nodejs\n                            :main          matthiasn.systems-toolbox.perf-runner\n                            :optimizations :advanced}}]})\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/component/helpers.cljc",
    "content": "(ns matthiasn.systems-toolbox.component.helpers\n  (:require\n    #?(:clj  [clojure.pprint :as pp]\n       :cljs [cljs.pprint :as pp])\n    #?(:cljs [cljs-uuid-utils.core :as uuid])))\n\n(defn now\n  \"Get milliseconds since epoch.\"\n  []\n  #?(:clj  (System/currentTimeMillis)\n     :cljs (.getTime (js/Date.))))\n\n(defn pp-str [data] (with-out-str (pp/pprint data)))\n\n(defn make-uuid\n  \"Get a random UUID.\"\n  []\n  #?(:clj  (java.util.UUID/randomUUID)\n     :cljs (uuid/make-random-uuid)))\n\n#?(:cljs (def request-animation-frame\n           (or (when (exists? js/window)\n                 (or (.-requestAnimationFrame js/window)\n                     (.-webkitRequestAnimationFrame js/window)\n                     (.-mozRequestAnimationFrame js/window)\n                     (.-msRequestAnimationFrame js/window)))\n               (fn [callback] (js/setTimeout callback 17)))))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/component/msg_handling.cljc",
    "content": "(ns matthiasn.systems-toolbox.component.msg-handling\n  #?(:cljs (:require-macros [cljs.core.async.macros :as cam :refer [go-loop]]\n             [cljs.core :refer [exists?]]))\n  (:require [matthiasn.systems-toolbox.spec :as spec]\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])\n             [matthiasn.systems-toolbox.component.helpers :as h]\n    #?(:clj  [io.aviso.exception :as ex])\n    #?(:clj  [clojure.core.match :refer [match]]\n       :cljs [cljs.core.match :refer-macros [match]])\n    #?(:clj  [clojure.core.async :as a :refer [chan go-loop]]\n       :cljs [cljs.core.async :as a :refer [chan]])\n             [clojure.set :as s]))\n\n(defn put-msg\n  \"On the JVM, always uses the blocking operation for putting messages on a\n   channel, as otherwise the system easily blows up when there are more than 1024\n   pending put operations. On the ClojureScript side, there is no equivalent of\n   the blocking put, so the asynchronous operation will have to do. But then,\n   more than 1024 pending operations in the browser wouldn't happen often,\n   if ever.\"\n  [channel msg]\n  #?(:clj  (a/>!! channel msg)\n     :cljs (a/put! channel msg)))\n\n(defn make-chan-w-buf\n  \"Create a channel with a buffer of the specified size and type.\"\n  [config]\n  (match config\n         [:sliding n] (chan (a/sliding-buffer n))\n         [:buffer n] (chan (a/buffer n))\n         :else (prn \"invalid: \" config)))\n\n(defn add-to-msg-seq\n  \"Function for adding the current component ID to the sequence that the message\n   has traversed thus far. The specified component IDs is either added when the\n   cmp-seq is empty in the case of an initial send or when the message is\n   received by a component. This avoids recording component IDs multiple times.\"\n  [msg-meta cmp-id in-out]\n  (let [cmp-seq (vec (:cmp-seq msg-meta))]\n    (if (not= (last cmp-seq) cmp-id)\n      (assoc-in msg-meta [:cmp-seq] (conj cmp-seq cmp-id))\n      msg-meta)))\n\n(defn default-state-pub-handler\n  \"Default handler function, can be replaced by a more application-specific\n   handler function, for example for resetting local component state when user\n   is not logged in.\"\n  [{:keys [observed msg-payload observed-xform]}]\n  (let [new-state (if observed-xform (observed-xform msg-payload) msg-payload)]\n    (when (not= @observed new-state)\n      (reset! observed new-state))\n    {}))\n\n(defn mk-handler-return-fn\n  \"Returns function for handling the return value of a handler function.\n  This returned map can contain :new-state, :emit-msg and :send-to-self keys.\"\n  [{:keys [state-reset-fn cfg put-fn cmp-id] :as cmp-map} in-chan msg-type msg-meta]\n  (fn [{:keys [new-state emit-msg emit-msgs send-to-self] :as handler-res}]\n    (l/debug cmp-id \"handler returned\")\n    (let [emit-msg-fn (fn [msg]\n                        (when (seq msg)\n                          (put-fn (with-meta msg (or (meta msg) msg-meta)))))]\n      (when new-state (when-let [state-spec (:state-spec cmp-map)]\n                        (when (:validate-state cfg)\n                          (assert (spec/valid-or-no-spec? state-spec new-state))\n                          (l/debug cmp-id \"returned state validated\")))\n                      (state-reset-fn new-state))\n      (when send-to-self (if (vector? (first send-to-self))\n                           (a/onto-chan in-chan send-to-self false)\n                           (a/onto-chan in-chan [send-to-self] false)))\n      (when emit-msg (if (vector? (first emit-msg))\n                       (doseq [msg-to-emit emit-msg] (emit-msg-fn msg-to-emit))\n                       (emit-msg-fn emit-msg)))\n      (let [res-keys (set (keys handler-res))\n            known-keys #{:new-state :emit-msg :emit-msgs :send-to-self}]\n        (when-not (s/subset? res-keys known-keys)\n          (l/warn \"Unknown keys in handler result. THIS IS PROBABLY NOT WHAT YOU WANT.\"\n                  (s/difference res-keys known-keys)\n                  cmp-id\n                  msg-type)))\n      (when emit-msgs\n        (l/warn \"DEPRECATED: emit-msgs, use emit-msg with a msg vector instead\")\n        (doseq [msg-to-emit emit-msgs]\n          (emit-msg-fn msg-to-emit))))))\n\n(defn msg-handler-loop\n  \"Constructs a map with a channel for the provided channel keyword, with the\n   buffer configured according to cfg for the channel keyword. Then starts loop\n   for taking messages off the returned channel and calling the provided\n   handler-fn with the msg.\n   Uses return value from handler function to change state and emit messages if\n   the respective keys :new-state and :emit-msg exist. Thus, the handler\n   function can be free from side effects.\n   For backwards compatibility, it is also possible interact with the put-fn and\n   the cmp-state atom directly, in which case the handler function itself would\n   produce side effects.\n   This, however, makes such handler functions somewhat more difficult to test.\"\n  [cmp-map chan-key]\n  (let [{:keys [handler-map all-msgs-handler state-pub-handler cfg cmp-id\n                firehose-chan snapshot-publish-fn unhandled-handler\n                state-snapshot-fn system-info]\n         :or   {handler-map {}}} cmp-map\n        in-chan (make-chan-w-buf (chan-key cfg))\n        onto-in-chan #(a/onto-chan in-chan % false)]\n    (go-loop []\n      (let [msg (a/<! in-chan)]\n        (l/debug cmp-id \"msg received\" msg)\n        (try\n          (let [recv-ts (h/now)\n                msg-meta (-> (merge (meta msg) {})\n                             (add-to-msg-seq cmp-id :in)\n                             (assoc-in [cmp-id :in-ts] (h/now))\n                             (assoc-in [:system-info] system-info))\n                [msg-type msg-payload] msg\n                handler-fn (msg-type handler-map)\n                put-fn (fn [msg]\n                         (let [msg-meta (merge msg-meta (meta msg))\n                               wrapped-put-fn (:put-fn cmp-map)]\n                           (wrapped-put-fn (with-meta msg msg-meta))))\n                msg-map-fn (fn []\n                             (merge\n                               cmp-map\n                               {:msg           (with-meta msg msg-meta)\n                                :msg-type      msg-type\n                                :msg-meta      msg-meta\n                                :put-fn        put-fn\n                                :msg-payload   msg-payload\n                                :onto-in-chan  onto-in-chan\n                                :current-state (state-snapshot-fn)}))\n                handler-return-fn (mk-handler-return-fn cmp-map in-chan msg-type msg-meta)\n                observed-state-handler (or state-pub-handler\n                                           default-state-pub-handler)]\n            (when (:validate-in cfg)\n              (assert (spec/valid-or-no-spec? msg-type msg-payload)))\n            (l/debug cmp-id \"msg validated\")\n            (when (= chan-key :sliding-in-chan)\n              (handler-return-fn (observed-state-handler (msg-map-fn)))\n              (l/debug cmp-id \"observed-state-handler done\")\n              (when (and (:snapshots-on-firehose cfg)\n                         (not= \"firehose\" (namespace msg-type)))\n                (put-msg firehose-chan\n                         [:firehose/cmp-recv-state {:cmp-id cmp-id :msg msg}]))\n              (l/debug cmp-id \"state snapshot published on firehose\")\n              (a/<! (a/timeout (:throttle-ms cfg))))\n            (when (= chan-key :in-chan)\n              (when (= msg-type :cmd/publish-state) (snapshot-publish-fn))\n              (when handler-fn\n                (handler-return-fn (handler-fn (msg-map-fn)))\n                (l/debug cmp-id \"handler function done\"))\n              (when unhandled-handler\n                (when-not (contains? handler-map msg-type)\n                  (handler-return-fn (unhandled-handler (msg-map-fn)))\n                  (l/debug cmp-id \"unhandled-handler function done\")))\n              (when all-msgs-handler\n                (handler-return-fn (all-msgs-handler (msg-map-fn)))\n                (l/debug cmp-id \"all-msgs-handler function done\"))\n              (when (and (:msgs-on-firehose cfg)\n                         (not= \"firehose\" (namespace msg-type)))\n                (let [now (h/now)]\n                  (put-msg firehose-chan [:firehose/cmp-recv\n                                          {:cmp-id      cmp-id\n                                           :firehose-id (h/make-uuid)\n                                           :system-info system-info\n                                           :msg         msg\n                                           :msg-meta    msg-meta\n                                           :duration    (- now recv-ts)\n                                           :ts          now}])))\n              (l/debug cmp-id \"received message published on firehose\")))\n          #?(:clj  (catch Exception e\n                     (l/error \"Exception in\" cmp-id \"when receiving message:\"\n                              (ex/format-exception e) (h/pp-str msg)))\n             :cljs (catch js/Object e\n                     (l/error e\n                              (str \"Exception in \" cmp-id \" when receiving message:\"\n                                   (h/pp-str msg)))))\n          #?(:clj (catch AssertionError e\n                    (l/error \"AssertionError in\" cmp-id \"when receiving message:\"\n                             (ex/format-exception e) (h/pp-str msg)))))\n        (recur)))\n    {chan-key in-chan}))\n\n(defn make-put-fn\n  \"The put-fn is used inside each component for emitting messages to the outside\n   world, from the component's point of view. All the component needs to know is\n   the type of the message.\n   Messages are vectors of two elements, where the first one is the type as a\n   namespaced keyword and the second one is the message payload, like this:\n     [:some/msg-type {:some \\\"data\\\"}]\n   Message payloads are typically maps or vectors, but they can also be strings,\n   primitive types, or nil. As long as they are local, they can even be any\n   type, e.g. a channel, but once we want messages to traverse some message\n   transport (WebSockets, some message queue), the types need to be limited to\n   what EDN or Transit can serialize.\n   Note that on component startup, this channel is not wired anywhere until the\n   'system-ready-fn' (below) is called, which pipes this channel into the actual\n   out-chan. Thus, components should not try call more messages than fit in the\n   buffer before the entire system is up.\"\n  [{:keys [cmp-id put-chan cfg firehose-chan system-info]}]\n  (let [put-one-msg\n        (fn [msg]\n          (try\n            (l/debug cmp-id \"put-fn called\")\n            (let [msg-meta (-> (merge (meta msg) {})\n                               (add-to-msg-seq cmp-id :out)\n                               (assoc-in [cmp-id :out-ts] (h/now)))\n                  corr-id (h/make-uuid)\n                  tag (or (:tag msg-meta) (h/make-uuid))\n                  tag-ts (or (:tag-ts msg-meta) (h/now))\n                  completed-meta (merge msg-meta {:corr-id corr-id\n                                                  :tag-ts  tag-ts\n                                                  :tag     tag})\n                  msg-w-meta (with-meta msg completed-meta)\n                  msg-type (first msg)\n                  msg-payload (second msg)\n                  msg-from-firehose? (= \"firehose\" (namespace msg-type))]\n              (when (:validate-out cfg)\n                (assert (spec/valid-or-no-spec?\n                          msg-type\n                          msg-payload\n                          #(put-msg firehose-chan\n                                    [:firehose/cmp-put\n                                     (merge %\n                                            {:cmp-id      cmp-id\n                                             :firehose-id (h/make-uuid)\n                                             :system-info system-info\n                                             :msg         msg-w-meta\n                                             :msg-meta    completed-meta\n                                             :ts          (h/now)})])))\n                (l/debug cmp-id \"put-fn msg validated\"))\n\n              (put-msg put-chan msg-w-meta)\n              (l/debug cmp-id \"put-fn: msg sent\")\n              ;; Not all components should emit firehose messages. For example, messages\n              ;; that process firehose messages should not do so again in order to avoid\n              ;; infinite messaging loops.\n              ;; This behavior can be configured when the component is fired up.\n              (when (:msgs-on-firehose cfg)\n                ;; Some components may emit firehose messages directly. One such example\n                ;; is the WebSockets component which can be used for relaying firehose\n                ;; messages, either from client to server or from server to client.\n                ;; In those cases, the emitted message should go on the firehose channel\n                ;; on the receiving end as such, not wrapped as other messages would\n                ;; (see the second case in the if-clause).\n                (if msg-from-firehose?\n                  (put-msg firehose-chan msg-w-meta)\n                  (put-msg firehose-chan\n                           [:firehose/cmp-put {:cmp-id      cmp-id\n                                               :firehose-id (h/make-uuid)\n                                               :system-info system-info\n                                               :msg         msg-w-meta\n                                               :msg-meta    completed-meta\n                                               :ts          (h/now)}]))\n                (l/debug cmp-id \"put-fn: msg put on firehose\")))\n            #?(:clj  (catch Exception e\n                       (l/error \"Exception in\" cmp-id \"when sending message:\"\n                                (ex/format-exception e) \"\\n\" (h/pp-str msg)))\n               :cljs (catch js/Object e\n                       (l/error (str \"Exception in \" cmp-id\n                                     \" when sending message:\\n\" (h/pp-str msg))\n                                e)))\n            #?(:clj (catch AssertionError e\n                      (l/error \"AssertionError in\" cmp-id \"when sending message:\"\n                               (ex/format-exception e) (h/pp-str msg))))))]\n    (fn [m]\n      (if (vector? (first m))\n        (doseq [msg m] (put-one-msg msg))\n        (put-one-msg m)))))\n\n(defn send-msg\n  \"Sends message to the specified component. By default, calls to this function\n   will block when no buffer space is available. Asynchronous handling is also\n   possible (JVM only), however the implications should be understood, such as\n   that core.async will throw an exception when there are more than 1024 pending\n   operations. Under most circumstances, blocking seems like the safer bet.\n   Note that, unless specified otherwise, the buffer for a component's in-chan\n   is of size one, see 'component-defaults'.\"\n  ([cmp msg] (send-msg cmp msg true))\n  ([cmp msg blocking?]\n   (let [in-chan (:in-chan cmp)]\n     #?(:clj  (if blocking?\n                (a/>!! in-chan msg)\n                (a/put! in-chan msg))\n        :cljs (a/put! in-chan msg)))))\n\n(defn send-msgs\n  \"Sends multiple messages to a component. Takes the component itself plus a\n   sequence with messages to send to the component.\n   Does not close the :in-chan of the component.\"\n  [cmp msgs]\n  (let [in-chan (:in-chan cmp)]\n    (a/onto-chan in-chan msgs false)))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/component.cljc",
    "content": "(ns matthiasn.systems-toolbox.component\n  (:require [matthiasn.systems-toolbox.spec :as s]\n    #?(:clj [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])\n    #?(:clj [io.aviso.exception :as ex])\n            [matthiasn.systems-toolbox.component.helpers :as h]\n            [matthiasn.systems-toolbox.component.msg-handling :as msg]\n    #?(:clj [clojure.core.async :as a :refer [chan]]\n       :cljs [cljs.core.async :as a :refer [chan]])))\n\n(defn now [] (h/now))\n(defn make-uuid [] (h/make-uuid))\n(def send-msg msg/send-msg)\n(def send-msgs msg/send-msgs)\n\n(def component-defaults\n  {:in-chan               [:buffer 1]\n   :sliding-in-chan       [:sliding 1]\n   :throttle-ms           1\n   :out-chan              [:buffer 1]\n   :sliding-out-chan      [:sliding 1]\n   :firehose-chan         [:buffer 1]\n   :publish-snapshots     true\n   :snapshots-on-firehose false\n   :msgs-on-firehose      false\n   :reload-cmp            true\n   :validate-in           false\n   :validate-out          true\n   :validate-state        true})\n\n(defn make-snapshot-publish-fn\n  \"Creates a function for publishing changes to the component state atom as\n   snapshot messages.\"\n  [{:keys [watch-state snapshot-xform-fn cmp-id sliding-out-chan cfg\n           firehose-chan]}]\n  (fn []\n    (when (:publish-snapshots cfg)\n      (let [snapshot @watch-state\n            snapshot-xform (if snapshot-xform-fn\n                             (snapshot-xform-fn snapshot)\n                             snapshot)\n            snapshot-msg (with-meta [:app/state snapshot-xform] {:from cmp-id})\n            state-firehose-chan (chan (a/sliding-buffer 1))]\n        (a/pipe state-firehose-chan firehose-chan)\n        (msg/put-msg sliding-out-chan snapshot-msg)\n        (when (:snapshots-on-firehose cfg)\n          (msg/put-msg state-firehose-chan\n                       [:firehose/cmp-publish-state {:cmp-id      cmp-id\n                                                     :firehose-id (h/make-uuid)\n                                                     :snapshot    snapshot-xform\n                                                     :ts          (now)}]))))))\n\n(defn detect-changes\n  \"Detect changes to the component state atom and then publish a snapshot using\n   the 'snapshot-publish-fn'.\"\n  [{:keys [watch-state cmp-id snapshot-publish-fn]}]\n  (try\n    (add-watch watch-state cmp-id (fn [_ _ _ _new-state]\n                                    (snapshot-publish-fn)))\n    #?(:clj  (catch Exception e\n               (l/error \"Failed watching atom\" cmp-id\n                        (ex/format-exception e)\n                        (h/pp-str watch-state)))\n       :cljs (catch js/Object e (l/error e \"Failed watching atom\" cmp-id\n                                         (h/pp-str watch-state))))))\n\n(defn make-system-ready-fn\n  \"This function is called by the switchboard that wired this component when all\n   other components are up and the channels between them connected. At this\n   point, messages that were accumulated on the 'put-chan' buffer since startup\n   are released. Also, the component state is published.\"\n  [{:keys [put-chan out-chan snapshot-publish-fn]}]\n  (fn []\n    (a/pipe put-chan out-chan)\n    (snapshot-publish-fn)))\n\n(defn initial-cmp-map\n  \"Assembles initial component map with core.async channels.\n    - :put-chan is used in component's put-fn, not connected at first\n    - :out-chan is the outgoing channel\n    - :firehose-chan is for where all messages go (for debugging)\n    - :sliding-out-chan is for state snapshots\n    \"\n  [cmp-map cfg]\n  (merge cmp-map\n         {:put-chan         (msg/make-chan-w-buf (:out-chan cfg))\n          :out-chan         (msg/make-chan-w-buf (:out-chan cfg))\n          :cfg              cfg\n          :firehose-chan    (msg/make-chan-w-buf (:firehose-chan cfg))\n          :sliding-out-chan (msg/make-chan-w-buf (:sliding-out-chan cfg))}))\n\n(defn make-component\n  \"Creates a component with attached in-chan, out-chan, sliding-in-chan and\n   sliding-out-chan.\n   It takes the initial state atom, the handler function for messages on\n   the in-chan, and the sliding-handler function, which handles messages on\n   :sliding-in-chan.\n   By default, in-chan and out-chan have standard buffers of size one, whereas\n   sliding-in-chan and sliding-out-chan have sliding buffers of size one.\n   The buffer sizes can be configured.\n   The sliding-channels are meant for events where only ever the latest version\n   is of interest, such as whenUI components rendering state snapshots from\n   other components.\n   Components send messages by using the put-fn, which is provided to the\n   component when creating it's initial state, and then subsequently in every\n   call to any of the handler functions. On every message send, a unique\n   correlation ID is attached to every message.\n   Also, messages are automatically assigned a tag, which is a unique ID that\n   doesn't change when a message flows through the system. This tag can also be\n   assigned manually by initially sending a message with the tag set on the\n   metadata, as this tag will not be touched by the library whenever it exists\n   already.\n   The configuration of a component comes from merging the component defaults\n   with the opts map that is passed on component creation the :opts key. The\n   order of the merge operation allows overwriting the default settings.\n   An observed-xform function can be provided, which transforms the observed\n   state before resetting the respective observed state. This function takes a\n   single argument, the observed state snapshot, and is expected to return a\n   single map with the transformed snapshot.\"\n  [{:keys [state-fn opts] :as cmp-map}]\n  (try\n    (let [cfg (merge component-defaults opts)\n          out-pub-chan (msg/make-chan-w-buf (:out-chan cfg))\n          cmp-map (initial-cmp-map cmp-map cfg)\n          put-fn (msg/make-put-fn cmp-map)\n          state-map\n          (merge\n            {:state    (atom {})\n             :observed (atom {})}\n            (when state-fn\n              (let [new-state (state-fn put-fn)]\n                (when-let [state-spec (:state-spec cmp-map)]\n                  (when (:validate-state cfg)\n                    (assert (s/valid-or-no-spec? state-spec @(:state new-state)))\n                    (l/debug (:cmp-id cmp-map) \"returned state validated\")))\n                new-state)))\n          state (:state state-map)\n          watch-state (if-let [watch (:watch opts)]         ; watchable atom\n                        (watch state)\n                        state)\n          cmp-map (merge cmp-map {:watch-state watch-state})\n          cmp-map (merge\n                    cmp-map\n                    {:snapshot-publish-fn (make-snapshot-publish-fn cmp-map)})\n          cmp-map\n          (merge cmp-map\n                 {:out-mult          (a/mult (:out-chan cmp-map))\n                  :firehose-mult     (a/mult (:firehose-chan cmp-map))\n                  :out-pub           (a/pub out-pub-chan first)\n                  :state-pub         (a/pub (:sliding-out-chan cmp-map) first)\n                  :cmp-state         state\n                  :observed          (:observed state-map)\n                  :put-fn            put-fn\n                  :system-ready-fn   (make-system-ready-fn cmp-map)\n                  :shutdown-fn       (:shutdown-fn state-map)\n                  :state-snapshot-fn (fn [] @watch-state)\n                  :state-reset-fn    (fn [new-state]\n                                       (reset! watch-state new-state))})]\n      (a/tap (:out-mult cmp-map) out-pub-chan)              ; connect out-pub-chan to out-mult\n      (detect-changes cmp-map)                              ; publish snapshots when changes are detected\n      (merge cmp-map\n             (msg/msg-handler-loop cmp-map :in-chan)\n             (msg/msg-handler-loop cmp-map :sliding-in-chan)))\n    #?(:clj  (catch Exception e (l/error \"Failed to init\" (:cmp-id cmp-map)\n                                         (ex/format-exception e))\n                                (System/exit 1))\n       :cljs (catch js/Object e (l/error \"Failed to init\" (:cmp-id cmp-map) e)))))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/handler_utils.cljc",
    "content": "(ns matthiasn.systems-toolbox.handler-utils\n  (:require [clojure.set :refer [subset?]]))\n\n(defn fwd-as\n  \"Creates a handler function that sends the payload of handled message as a new\n   message type while discarding any metadata on the original message.\"\n  [new-type]\n  (fn\n    [{:keys [put-fn msg-payload]}]\n    (put-fn [new-type msg-payload])))\n\n(defn fwd-as-w-meta\n  \"Creates a handler function that sends the payload of the handled message as a\n   new message type preserving metadata of the original message.\"\n  [new-type]\n  (fn\n    [{:keys [put-fn msg-payload msg-meta]}]\n    (put-fn (with-meta [new-type msg-payload] msg-meta))))\n\n(defn run-handler\n  \"Runs another handler function with a new message and otherwise the same\n   context.\"\n  ([handler-key msg-payload msg-map]\n   (let [handler-fn (handler-key (:handler-map msg-map))]\n     (when handler-fn (handler-fn (assoc msg-map :msg-type handler-key\n                                                 :msg-payload msg-payload\n                                                 :msg [handler-key msg-payload])))))\n  ([handler-key msg-map]\n   ;; A common mistake is to call (run-handler) with a handler-key and msg-payload, but not with\n   ;; a msg-map. In such case, this fn would do nothing and fail silently, leaving user in a blank.\n   ;; Assert to verify against that.\n   (assert (subset? #{:cmp-state :msg-meta :msg-payload} (set (keys msg-map)))\n           \"(run-handler) invoked with invalid arguments. Make sure you did pass msg-map.\")\n   (let [handler-fn (handler-key (:handler-map msg-map))]\n     (when handler-fn (handler-fn (assoc msg-map :msg-type handler-key\n                                                 :msg [handler-key]))))))\n\n(defn assoc-in-cmp\n  \"Helper for creating a function that sets value in component atom in given path.\"\n  [path]\n  (fn [{:keys [current-state msg-payload]}]\n    {:new-state (assoc-in current-state path msg-payload)}))\n\n(defn update-in-cmp\n  \"Helper for creating a function that updates value in component atom in given\n   path by applying f.\"\n  [path f]\n  (fn [{:keys [current-state]}]\n    {:new-state (update-in current-state path f)}))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/log.cljc",
    "content": "(ns matthiasn.systems-toolbox.log\n  \"Some helpers for logging in ClojureScript\"\n  (:require [clojure.string :as s]\n            [matthiasn.systems-toolbox.component.helpers :as h]))\n\n(def ^{:dynamic true} *debug-enabled* false)\n(defn enable-debug-log! [] (set! *debug-enabled* true))\n\n(defn info [& args]\n  (println (s/join \" \" args)))\n\n(defn warn [& args]\n  (println (str \"WARN: \" (s/join \" \" args))))\n\n(defn error [& args]\n  (println (str \"ERROR: \" (s/join \" \" args))))\n\n(defn debug [& args]\n  (when *debug-enabled*\n    (println (str (h/now) \" DEBUG: \" (s/join \" \" args)))))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/scheduler.cljc",
    "content": "(ns matthiasn.systems-toolbox.scheduler\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go-loop]]))\n  (:require [matthiasn.systems-toolbox.component :as comp]\n            #?(:clj  [clojure.tools.logging :as l]\n               :cljs [matthiasn.systems-toolbox.log :as l])\n            #?(:clj [clojure.core.async :refer [<! go-loop timeout]])\n            #?(:cljs [cljs.core.async :refer [<! timeout]])))\n\n;;; Systems Toolbox - Scheduler Subsystem\n\n;;; This namespace describes a component / subsystem for scheduling the sending of messages that\n;;; can then elsewhere trigger some action.\n\n;;; Example: we want to let web clients know how many documents we have in a database so they can\n;;; update the UI accordingly. The subsystem handling the database connectivity has the logic for\n;;; figuring out how many documents there are when receiving a request, but no notion of repeatedly\n;;; emitting this information itself. Now say we want this every 10 seconds. We tell the scheduler\n;;; to emit the message type that will trigger the request every 10 seconds, and that's it.\n\n;;; Internally, each scheduled event starts a go-loop with a timeout of the specified duration\n;;; while recording the scheduled event in the state atom. Post-timeout, it is checked if the\n;;; message is still scheduled to be sent and if so, the specified message is sent.\n\n;;; Scheduled events can be deleted. TODO: implement\n\n;;; When the same, optional :id is set on multiple message sent to scheduler component, only\n;;; first of those messages will result in scheduling a new timer.\n\n;;; TODO: record start time so that the scheduled time can be shown in UI. Platform-specific implementation.\n\n;;; WARNING: timeouts specified here are not precise unless proven otherwise. Even if timeouts\n;;; happen to have a sufficiently precise duration, the go-loop in which they run (and the\n;;; associated thread pool) may be busy otherwise and delay the next iteration.\n\n(defn start-loop\n  \"Starts a loop for sending messages at set intervals.\"\n  [{:keys [current-state cmp-state put-fn msg-meta msg-payload]}]\n  (let [timeout-ms (:timeout msg-payload)\n        msg-to-send (:message msg-payload)\n        msg-meta (merge\n                   (update-in msg-meta [:cmp-seq] #(vec (take-last 2 %)))\n                   (meta msg-to-send)\n                   (:msg-meta msg-payload))\n        msg-to-send (with-meta msg-to-send msg-meta)\n        scheduler-id (or (:id msg-payload) (first msg-to-send))]\n    (if (get-in current-state [:active-timers scheduler-id])\n      (l/debug (str \"Timer \" scheduler-id \" already scheduled - ignoring.\"))\n      (do (when (:initial msg-payload) (put-fn msg-to-send))\n          (go-loop []\n            (<! (timeout timeout-ms))\n            (let [active-timer (get-in @cmp-state [:active-timers scheduler-id])]\n              (put-fn msg-to-send)\n              (if active-timer\n                (if (:repeat active-timer)\n                  (recur)\n                  (swap! cmp-state update :active-timers dissoc scheduler-id))\n                (put-fn [:info/deleted-timer scheduler-id]))))\n          {:new-state (assoc-in current-state [:active-timers scheduler-id] msg-payload)}))))\n\n(defn stop-loop\n  \"Stops a an loop that was previously scheduled.\"\n  [{:keys [current-state msg-payload]}]\n  (let [scheduler-id (:id msg-payload)]\n    (if (get-in current-state [:active-timers scheduler-id])\n      {:new-state (update-in current-state [:active-timers scheduler-id] msg-payload)}\n      (l/warn (str \"Timer with id: \" (:id msg-payload) \" not found - did not stop.\")))))\n\n(defn state-fn\n  [_put-fn]\n  (let [initial-state {:active-timers {}}\n        state-atom (atom initial-state)]\n    {:state       state-atom\n     :shutdown-fn #(reset! state-atom initial-state)}))\n\n(defn cmp-map\n  {:added \"0.3.1\"}\n  [cmp-id]\n  {:cmp-id      cmp-id\n   :state-fn    state-fn\n   :handler-map {:schedule/new        start-loop\n                 :schedule/delete     stop-loop\n                 :cmd/schedule-new    start-loop\n                 :cmd/schedule-delete stop-loop}\n   :opts        {:reload-cmp false}})\n\n(defn component\n  {:deprecated \"0.3.1\"}\n  [cmp-id]\n  (comp/make-component (cmp-map cmp-id)))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/spec.cljc",
    "content": "(ns matthiasn.systems-toolbox.spec\n  (:require  [expound.alpha :as exp]\n    #?(:clj  [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])))\n\n(defn valid-or-no-spec?\n  \"If spec exists, validate spec and warn if x is invalid, with detailed\n   explanation. Also puts that information on firehose for use in inspect.\"\n  ([spec x] (valid-or-no-spec? spec x nil))\n  ([spec x firehose-put]\n   (if (contains? (s/registry) spec)\n     (if (s/valid? spec x)\n       true\n       (let [validation-error (exp/expound-str spec x)]\n         (l/error validation-error)\n         (when firehose-put (firehose-put {:spec-error validation-error}))\n         false))\n     (let [warning (str \"UNDEFINED SPEC \" spec)]\n       (l/warn warning)\n       (when firehose-put (firehose-put {:spec-warning warning}))\n       true))))\n\n(defn namespaced-keyword? [k] (and (keyword? k) (namespace k)))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Message Spec\n(s/def :systems-toolbox/msg-spec\n  (s/or :no-payload (s/cat :msg-type namespaced-keyword?)\n        :payload (s/cat :msg-type namespaced-keyword?\n                        :msg-payload (s/or :map-payload map?\n                                           :vector-payload vector?\n                                           :nil-payload nil?\n                                           :bool-payload boolean?\n                                           :number-payload number?\n                                           :string-payload string?\n                                           :keyword-payload keyword?))))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Firehose Spec\n(s/def :st.firehose/cmp-id namespaced-keyword?)\n(s/def :st.firehose/msg :systems-toolbox/msg-spec)\n(s/def :st.firehose/ts pos-int?)\n(s/def :st.firehose/snapshot map?)\n\n(s/def :firehose/cmp-recv\n  (s/keys :req-un [:st.firehose/cmp-id\n                   :st.firehose/msg\n                   :st.firehose/msg-meta\n                   :st.firehose/ts]))\n\n(s/def :firehose/cmp-put :firehose/cmp-recv)\n\n(s/def :firehose/cmp-publish-state\n  (s/keys :req-un [:st.firehose/cmp-id\n                   :st.firehose/snapshot\n                   :st.firehose/ts]))\n\n(s/def :firehose/cmp-recv-state\n  (s/keys :req-un [:st.firehose/cmp-id\n                   :st.firehose/msg]))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Scheduler Spec\n(s/def :st.schedule/timeout pos-int?)\n(s/def :st.schedule/message :systems-toolbox/msg-spec)\n(s/def :st.schedule/id keyword?)\n(s/def :st.schedule/repeat boolean?)\n(s/def :st.schedule/initial boolean?)\n\n(s/def :schedule/new\n  (s/keys :req-un [:st.schedule/timeout\n                   :st.schedule/message]\n          :opt-un [:st.schedule/id\n                   :st.schedule/repeat\n                   :st.schedule/initial]))\n\n(s/def :schedule/delete\n  (s/keys :req-un [:st.schedule/id]))\n\n(s/def :cmd/schedule-new :schedule/new)\n(s/def :cmd/schedule-delete :schedule/delete)\n\n(s/def :info/deleted-timer keyword?)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; App State Specs\n;; TODO: define specific specs for a component in order to validate the :new-state returned by handler functions\n(s/def :app/state map?)\n\n(s/def :cmd/publish-state nil?)\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard/helpers.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard.helpers\n  \"Helper functions used by switchboard.\"\n  (:require  [matthiasn.systems-toolbox.spec :as spec]\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])))\n\n(defn cartesian-product\n  \"All the ways to take one item from each sequence.\n  Borrowed from: https://github.com/clojure/math.combinatorics/blob/master/src/main/clojure/clojure/math/combinatorics.clj\n  Reason: https://groups.google.com/forum/#!topic/clojure-dev/PDyOklDEv7Y\"\n  [& seqs]\n  (let [v-original-seqs (vec seqs)\n        step\n        (fn step [v-seqs]\n          (let [increment\n                (fn [v-seqs]\n                  (loop [i (dec (count v-seqs)), v-seqs v-seqs]\n                    (when-not (= i -1)\n                      (if-let [rst (next (v-seqs i))]\n                        (assoc v-seqs i rst)\n                        (recur (dec i) (assoc v-seqs i (v-original-seqs i)))))))]\n            (when v-seqs\n              (cons (map first v-seqs)\n                    (lazy-seq (step (increment v-seqs)))))))]\n    (when (every? seq seqs)\n      (lazy-seq (step v-original-seqs)))))\n\n(defn cmp-ids-set\n  \"Returns a set with component IDs.\"\n  [val]\n  (cond (set? val) val\n        (spec/namespaced-keyword? val) #{val}\n\n        (vector? val)\n        (do (l/warn \"Use of vector is deprecated, use a set instead:\" val)\n            (set val))))"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard/init.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard.init\n  (:require [matthiasn.systems-toolbox.component :as comp]\n            [matthiasn.systems-toolbox.switchboard.spec]\n    #?(:clj [clojure.core.async :refer [put! tap untap-all untap unsub-all close!]]\n       :cljs [cljs.core.async :refer [put! tap untap-all untap unsub-all close!]])\n    #?(:clj [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])\n    #?(:clj [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])))\n\n(defn cmp-maps-set\n  \"Returns a set with component maps.\"\n  [val]\n  (cond\n    (set? val) val\n\n    (vector? val)\n    (do (l/warn \"Use of vector is deprecated, use a set instead:\" val)\n        (set val))\n\n    :else #{val}))\n\n(defn wire-or-init-comp\n  \"Either wire existing and already instantiated component or instantiate a\n   component from a component map.\n   Also capable of reloading component, e.g. when using Figwheel on the client\n   side.\n   When a previous component with the same name exists, this function first of\n   all unwires that previous component by unsubscribing and untapping all\n   connected channels. Then, the state of that previous component is used in the\n   new component in order to provide a smooth developer experience.\n   Finally, the new component is tapped into the switchboard's firehose and the\n   component is also asked to publish its state once (also useful for Figwheel).\"\n  [init?]\n  (fn [{:keys [current-state msg-payload cmp-id system-info]}]\n    (let [cmp-maps-set (set (filter identity (cmp-maps-set msg-payload)))\n          reducer-fn\n          (fn [acc cmp]\n            (let [cmp-id-to-wire (:cmp-id cmp)\n                  firehose-chan (:firehose-chan (cmp-id (:components current-state)))\n                  reload? (:reload-cmp (merge comp/component-defaults (:opts cmp)))\n                  prev-cmp (get-in current-state [:components cmp-id-to-wire])]\n              (when prev-cmp\n                (untap-all (:firehose-mult prev-cmp))\n                (untap (:firehose-mult (cmp-id (:components current-state)))\n                       (:in-chan prev-cmp))\n                (unsub-all (:out-pub prev-cmp))\n                (unsub-all (:state-pub prev-cmp)))\n\n              (when (and prev-cmp reload?)\n                (when-let [shutdown-fn (:shutdown-fn prev-cmp)]\n                  (shutdown-fn)))\n              (let [cmp (assoc-in cmp [:system-info] system-info)\n                    cmp (if (or (not prev-cmp) reload?)\n                          (if init? (comp/make-component cmp) cmp)\n                          prev-cmp)]\n                (if cmp\n                  (let [in-chan (:in-chan cmp)\n                        new-state (-> acc\n                                      (assoc-in [:components cmp-id-to-wire] cmp)\n                                      (update-in [:fh-taps] conj\n                                                 {:from cmp-id-to-wire\n                                                  :to   cmp-id\n                                                  :type :fh-tap}))]\n                    (when-let [prev-state (:watch-state prev-cmp)]\n                      (reset! (:watch-state cmp) @prev-state))\n                    (tap (:firehose-mult cmp) firehose-chan)\n                    (let [known-cmp-ids (set (keys (:components new-state)))]\n                      (s/def :st.switchboard/cmp known-cmp-ids))\n                    (put! in-chan [:cmd/publish-state])\n                    new-state)\n                  acc))))\n          new-state (reduce reducer-fn current-state cmp-maps-set)]\n      {:new-state new-state})))\n\n(defn shutdown-all\n  \"Call shutdown function on each component to prepare for reload.\"\n  [{:keys [current-state]}]\n  (let [cmps (:components current-state)]\n    (doseq [cmp (vals cmps)]\n      (when-let [shutdown-fn (:shutdown-fn cmp)]\n        (shutdown-fn)))))\n\n(defn shutdown-cmp\n  \"Call shutdown function on specified component.\"\n  [{:keys [current-state msg-payload]}]\n  (let [cmp (-> current-state :components msg-payload)\n        new-state (update-in current-state [:components] dissoc msg-payload)]\n    (when-let [shutdown-fn (:shutdown-fn cmp)]\n      (shutdown-fn))\n    {:new-state new-state\n     :emit-msg  [:switchboard/status {:cmd    :shutdown\n                                      :status :success}]}))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard/observe.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard.observe\n  (:require  [matthiasn.systems-toolbox.switchboard.helpers :as h]\n    #?(:clj  [clojure.core.async :refer [sub]]\n       :cljs [cljs.core.async :refer [sub]])))\n\n(defn observe-state\n  \"Handler function for letting one component observe the state of another.\"\n  [{:keys [current-state msg-payload]}]\n  (let [{:keys [from to]} msg-payload\n        reducer-fn\n        (fn [acc to]\n          (let [pub-comp (from (:components acc))\n                sub-comp (to (:components acc))]\n            (sub (:state-pub pub-comp) :app/state (:sliding-in-chan sub-comp))\n            (update-in acc [:subs] conj {:from     from\n                                         :to       to\n                                         :msg-type :app/state\n                                         :type     :sub})))]\n    {:new-state (reduce reducer-fn current-state (h/cmp-ids-set to))}))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard/route.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard.route\n  (:require  [matthiasn.systems-toolbox.spec :as spec]\n             [matthiasn.systems-toolbox.switchboard.helpers :as h]\n    #?(:clj  [clojure.core.async :refer [chan pipe sub tap]]\n       :cljs [cljs.core.async :refer [chan pipe sub tap]])\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])\n             [clojure.set :as set]))\n\n(defn subscribe-fn\n  \"Subscribe component to a specified publisher.\"\n  [from to pred]\n  (fn [current-state msg-type]\n    (let [in-chan (:in-chan (to (:components current-state)))\n          target-chan (if pred (let [filtered-chan (chan 1 (filter pred))]\n                                 (pipe filtered-chan in-chan)\n                                 filtered-chan)\n                               in-chan)]\n      (sub (:out-pub (from (:components current-state))) msg-type target-chan)\n      (update-in current-state [:subs] conj {:from from\n                                             :to to\n                                             :msg-type msg-type\n                                             :type :sub}))))\n\n(defn route-handler\n  \"Creates subscriptions between one component's out-pub and another component's\n   in-chan.\n   Requires a map with at least the :from and :to keys.\n   Also, routing can be limited to message types specified under the :only\n   keyword. Here, either a single message type or a vector with multiple message\n   types can be used.\"\n  [{:keys [current-state msg-payload]}]\n  {:pre (empty? (set/intersection (h/cmp-ids-set (:from msg-payload))\n                                  (h/cmp-ids-set (:to msg-payload))))}\n  (let [{:keys [from to only pred]} msg-payload\n        connections (h/cartesian-product (h/cmp-ids-set from) (h/cmp-ids-set to))\n\n        subscribe-reducer-fn\n        (fn [acc [from to]]\n          (let [handled-messages (keys (:handler-map (to (:components acc))))\n                msg-types (if only (flatten [only]) (vec handled-messages))\n                subscribe (subscribe-fn from to pred)]\n            (reduce subscribe acc msg-types)))]\n    {:new-state (reduce subscribe-reducer-fn current-state connections)}))\n\n;; TODO: implement filtering with comparable semantics as in route-handler, see issue #34\n(defn route-all-handler\n  \"Connects two components where ALL messages are routed to recipient(s), not\n   only those for which there is a specific handler. This results in both the\n   all-msgs-handler receiving all messages and the unhandled-handler receiving\n   those for which there is no handler.\"\n  [{:keys [current-state msg-payload]}]\n  {:pre (empty? (set/intersection (h/cmp-ids-set (:from msg-payload))\n                                  (h/cmp-ids-set (:to msg-payload))))}\n  (let [{:keys [from to pred]} msg-payload\n        components (:components current-state)\n        connections (h/cartesian-product (h/cmp-ids-set from) (h/cmp-ids-set to))\n        reducer-fn (fn [acc [from to]]\n                     (let [in-chan (:in-chan (to components))\n                           target-chan (if pred\n                                         (let [filtered-ch (chan 1 (filter pred))]\n                                           (pipe filtered-ch in-chan)\n                                           filtered-ch)\n                                         in-chan)]\n                       (tap (:out-mult (from components)) target-chan)\n                       (update-in acc [:taps] conj {:from from\n                                                    :to   to\n                                                    :type :tap})))]\n    {:new-state (reduce reducer-fn current-state connections)}))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard/spec.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard.spec\n  (:require  [matthiasn.systems-toolbox.spec :as sts]\n    #?(:clj  [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Spec for :cmd/init-comp\n(s/def :st.switchboard.init/cmp-id sts/namespaced-keyword?)\n(s/def :st.switchboard.init/state-fn fn?)\n(s/def :st.switchboard.init/handler-map\n  (s/nilable (s/map-of sts/namespaced-keyword? fn?)))\n(s/def :st.switchboard.init/all-msgs-handler fn?)\n(s/def :st.switchboard.init/state-pub-handler (s/nilable fn?))\n(s/def :st.switchboard.init/observed-xform (s/nilable fn?))\n(s/def :st.switchboard.init/opts map?)\n(s/def :st.switchboard.init/state-spec sts/namespaced-keyword?)\n\n(s/def :st.switchboard.init/cmp-map\n  (s/keys :req-un [:st.switchboard.init/cmp-id]\n          :opt-un [:st.switchboard.init/state-fn\n                   :st.switchboard.init/handler-map\n                   :st.switchboard.init/all-msgs-handler\n                   :st.switchboard.init/state-pub-handler\n                   :st.switchboard.init/observed-xform\n                   :st.switchboard.init/opts\n                   :st.switchboard.init/state-spec]))\n\n(s/def :cmd/init-comp (s/or :single-cmp :st.switchboard.init/cmp-map\n                            :multiple-cmps (s/+ :st.switchboard.init/cmp-map)))\n\n(s/def :st.switchboard/cmp sts/namespaced-keyword?)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Spec for :cmd/route\n(s/def :st.switchboard.route/from (s/or :single :st.switchboard/cmp\n                                        :multiple (s/+ :st.switchboard/cmp)))\n(s/def :st.switchboard.route/to (s/or :single :st.switchboard/cmp\n                                      :multiple (s/+ :st.switchboard/cmp)))\n(s/def :st.switchboard.route/only :st.switchboard/cmp)\n(s/def :st.switchboard.route/pred fn?)\n\n(s/def :cmd/route\n  (s/keys :req-un [:st.switchboard.route/from\n                   :st.switchboard.route/to]\n          :opt-un [:st.switchboard.route/only\n                   :st.switchboard.route/pred]))\n\n(s/def :cmd/route-all\n  (s/keys :req-un [:st.switchboard.route/from\n                   :st.switchboard.route/to]\n          :opt-un [:st.switchboard.route/pred]))\n\n(s/def :cmd/attach-to-firehose sts/namespaced-keyword?)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Spec for :cmd/observe-state\n(s/def :st.switchboard.observe/from :st.switchboard/cmp)\n(s/def :st.switchboard.observe/to (s/or :single :st.switchboard/cmp\n                                     :multiple (s/+ :st.switchboard/cmp)))\n(s/def :cmd/observe-state\n  (s/keys :req-un [:st.switchboard.observe/from\n                   :st.switchboard.observe/to]))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Spec for :cmd/send\n(s/def :st.switchboard-send/to :st.switchboard/cmp)\n(s/def :st.switchboard-send/msg :systems-toolbox/msg-spec)\n(s/def :cmd/send\n  (s/keys :req-un [:st.switchboard-send/to\n                   :st.switchboard-send/msg]))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Misc Switchboard Specs\n;; TODO: define structure of component map. Here, the switchboard is passed.\n(s/def :cmd/self-register map?)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Switchboard State Spec\n(s/def :st.switchboard/components (s/map-of sts/namespaced-keyword? map?))\n(s/def :st.switchboard.sub/from :st.switchboard/cmp)\n(s/def :st.switchboard.sub/to :st.switchboard/cmp)\n(s/def :st.switchboard.sub/msg-type sts/namespaced-keyword?)\n(s/def :st.switchboard.sub/type #{:sub})\n\n(s/def :st.switchboard/sub-map\n  (s/keys :req-un [:st.switchboard.sub/from\n                   :st.switchboard.sub/to\n                   :st.switchboard.sub/msg-type\n                   :st.switchboard.sub/type]))\n(s/def :st.switchboard/subs (s/and (s/coll-of :st.switchboard/sub-map) set?))\n\n(s/def :st.switchboard.fh-tap/type #{:fh-tap})\n(s/def :st.switchboard/fh-tap-map\n  (s/keys :req-un [:st.switchboard.sub/from\n                   :st.switchboard.sub/to\n                   :st.switchboard.fh-tap/type]))\n(s/def :st.switchboard/fh-taps (s/and (s/coll-of :st.switchboard/fh-tap-map) set?))\n\n(s/def :st.switchboard/state-spec\n  (s/keys :req-un [:st.switchboard/components\n                   :st.switchboard/subs\n                   :st.switchboard/taps\n                   :st.switchboard/fh-taps]))\n"
  },
  {
    "path": "src/cljc/matthiasn/systems_toolbox/switchboard.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard\n  (:require\n    [matthiasn.systems-toolbox.component :as comp]\n    [matthiasn.systems-toolbox.switchboard.route :as rt]\n    [matthiasn.systems-toolbox.switchboard.observe :as obs]\n    [matthiasn.systems-toolbox.switchboard.init :as i]\n    [matthiasn.systems-toolbox.component.helpers :as h]\n    #?(:clj  [clojure.core.async :refer [put! chan pipe sub tap]]\n       :cljs [cljs.core.async :refer [put! chan pipe sub tap]])\n    #?(:clj  [clojure.pprint :as pp]\n       :cljs [cljs.pprint :as pp])\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])\n    #?(:clj  [io.aviso.exception :as ex])\n    #?(:clj  [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])))\n\n(defn self-register\n  \"Registers switchboard itself as another component that can be wired. Useful\n   for communication with the outside world / within hierarchies where a\n   subsystem has its own switchboard.\"\n  [{:keys [cmp-state msg-payload cmp-id]}]\n  (swap! cmp-state assoc-in [:components cmp-id] msg-payload)\n  (swap! cmp-state assoc-in [:switchboard-id] cmp-id)\n  {})\n\n(defn mk-state [_put-fn]\n  {:state (atom {:components {}\n                 :subs       #{}\n                 :taps       #{}\n                 :fh-taps    #{}})})\n\n(defn attach-to-firehose\n  \"Attaches a component to firehose channel. For example for observational\n   components.\"\n  [{:keys [current-state msg-payload cmp-id]}]\n  (let [to msg-payload\n        sw-firehose-mult (:firehose-mult (cmp-id (:components current-state)))\n        to-comp (to (:components current-state))]\n    (try\n      (do\n        (tap sw-firehose-mult (:in-chan to-comp))\n        {:new-state (update-in current-state [:fh-taps] conj {:from cmp-id\n                                                              :to   to\n                                                              :type :fh-tap})})\n      #?(:clj  (catch Exception e\n                 (l/error \"Could not create tap: \" cmp-id \" -> \" to \" - \"\n                          (ex/format-exception e)))\n         :cljs (catch js/Object e (l/error \"Could not create tap: \" cmp-id\n                                           \" -> \" to \" - \" e))))))\n\n(defn send-to\n  \"Send message to specified component.\"\n  [{:keys [cmp-state msg-payload]}]\n  (let [{:keys [to msg]} msg-payload\n        dest-comp (to (:components @cmp-state))]\n    (put! (:in-chan dest-comp) msg))\n  {})\n\n(defn wire-all-out-channels\n  \"Function for calling the system-ready-fn on each component, which will pipe\n   the channel used by the put-fn to the out-chan when the system is connected.\n   Otherwise, messages sent before all channels are wired would get lost.\"\n  [{:keys [cmp-state]}]\n  (doseq [[_ cmp] (:components @cmp-state)]\n    ((:system-ready-fn cmp))))\n\n(def handler-map\n  {:cmd/route              rt/route-handler\n   :cmd/route-all          rt/route-all-handler\n   :cmd/wire-comp          (i/wire-or-init-comp false)\n   :cmd/init-comp          (i/wire-or-init-comp true)\n   :cmd/shutdown-all       i/shutdown-all\n   :cmd/shutdown           i/shutdown-cmp\n   :cmd/attach-to-firehose attach-to-firehose\n   :cmd/self-register      self-register\n   :cmd/observe-state      obs/observe-state\n   :cmd/send               send-to\n   :status/system-ready    wire-all-out-channels})\n\n(defn xform-fn\n  \"Transformer function for switchboard state snapshot. Allows serialization of\n   snapshot for sending, e.g. over WebSockets or other transports.\"\n  [m]\n  (update-in m [:components] (fn [cmps]\n                               (into {} (mapv (fn [[k v]] [k k]) cmps)))))\n\n(defn component\n  \"Creates a switchboard component that wires individual components together\n   into a communicating system.\"\n  ([switchboard-id]\n   (component switchboard-id {}))\n  ([switchboard-id cmp-opts]\n   (let [system-info (merge {:system-id (str (h/make-uuid))\n                             :node-type (str (h/make-uuid))\n                             :node-id   (str (h/make-uuid))}\n                            (select-keys cmp-opts [:system-id :node-type :node-id]))\n         switchboard (comp/make-component\n                       {:cmp-id            switchboard-id\n                        :state-fn          mk-state\n                        :handler-map       handler-map\n                        :system-info       system-info\n                        :state-spec        :st.switchboard/state-spec\n                        :opts              (merge {:msgs-on-firehose      false\n                                                   :snapshots-on-firehose true}\n                                                  cmp-opts)\n                        :snapshot-xform-fn xform-fn})\n         sw-in-chan (:in-chan switchboard)]\n     (put! sw-in-chan [:cmd/self-register switchboard])\n     switchboard)))\n\n(defn send-cmd\n  \"Send message to the specified switchboard component.\"\n  [switchboard cmd]\n  (put! (:in-chan switchboard) cmd))\n\n(defn send-mult-cmd\n  \"Send messages to the specified switchboard component.\"\n  [switchboard cmds]\n  (doseq [cmd cmds] (when cmd (put! (:in-chan switchboard) cmd)))\n  (put! (:in-chan switchboard) [:status/system-ready]))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/component_test.cljc",
    "content": "(ns matthiasn.systems-toolbox.component-test\n  \"Interact with components by sending some messages directly, see them handled correctly.\"\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require [matthiasn.systems-toolbox.test-spec]\n    #?(:clj  [clojure.test :refer [deftest testing is]]\n       :cljs [cljs.test :refer-macros [deftest testing is]])\n    #?(:clj  [clojure.core.async :refer [<! chan put! go timeout promise-chan tap]]\n       :cljs [cljs.core.async :refer [<! chan put! timeout promise-chan tap]])\n             [matthiasn.systems-toolbox.test-promise :as tp]\n             [matthiasn.systems-toolbox.component :as component]\n    #?(:clj  [clojure.tools.logging :as log]\n       :cljs [matthiasn.systems-toolbox.log :as log])))\n\n(deftest cmp-all-msgs-handler\n  \"Tests that a very simple component that only has a handler for all messages regardless of type receives all\n  messages sent to the component. State management of the component is not used here, instead we keep track\n  of the messages in an atom that's external to the component and that the handler function has access to.\n  A promise is used here which is delivered on when the message count received matches those sent. This does\n  not tell us anything about the order yet, but it is still very useful when waiting for all messages to be\n  delivered. In the subsequent assertion, we then check if the received messages are complete and in the\n  expected order.\"\n  (let [msgs-recvd (atom [])\n        cnt 1000\n        msgs-to-send (vec (range cnt))\n        all-recvd (promise-chan)\n        cmp (component/make-component\n              {:cmp-id           :test/cmp\n               :all-msgs-handler (fn [{:keys [msg-payload put-fn]}]\n                                   (swap! msgs-recvd conj msg-payload)\n                                   (when (= cnt (count @msgs-recvd))\n                                     (put-fn [:test/done]))\n                                   {})})\n        ready-fn (:system-ready-fn cmp)]\n\n    (tap (:out-mult cmp) all-recvd)\n    (ready-fn)\n\n    (component/send-msgs cmp (map (fn [m] [:some/type m]) msgs-to-send))\n\n    (tp/w-timeout\n      5000\n      (go\n        (testing \"all messages received\"\n          (is (= [:test/done] (<! all-recvd))))\n        (testing \"sent messages equal received messages\"\n          (is (= msgs-to-send @msgs-recvd)))))))\n\n(defn cmp-all-msgs-handler-cmp-state-fn\n  \"Like cmp-all-msgs-handler test, except that the handler function here acts on the component state provided\n  in the map that the :all-msgs-handler function is called with.\n\n  The number of messages sent in this test is somewhat arbitrarily, except that I wanted a) more than the 1024 pending\n  puts that core.async allows and b) have confidence that a larger number of messages can be processed in\n  very short time. Here, I test that more than 1K messages are processed per second. While timing can be problematic\n  in tests, my laptop processes around 80K messages/s on the JVM (JDK 1.8) after some warming up and slightly slower\n  in both Firefox and Chrome with around 70K msgs/s. If the rate dropped below 1K on ANY machine, that would indeed\n  be a problem IMHO. These tests ought to quickly make visible any changes in the library that have an unfavorable\n  complexity and thus provide some sanity check. Also, since the CI environment records these results, they can be\n  compared over time.\n\n  Running this test multiple times gets us a better understanding how performance increases on subsequent runs,\n  likely because of JIT compilation and other optimizations in the runtime.\"\n  []\n  (let [cnt 1000\n        state (atom 0)\n        vals-to-send (vec (range cnt))\n        msgs-to-send (map (fn [m] [:some/type m]) vals-to-send)\n        all-recvd (promise-chan)\n        res (reduce + (range cnt))\n        cmp (component/make-component\n              {:state-fn         (fn [_put-fn] {:state state})\n               :all-msgs-handler (fn [{:keys [msg-payload current-state]}]\n                                   (let [new-state (+ current-state msg-payload)]\n                                     {:new-state new-state\n                                      :emit-msg  (when (= res new-state)\n                                                   [[:test/done]])}))})\n        start-ts (component/now)\n        ready-fn (:system-ready-fn cmp)]\n    (tap (:out-mult cmp) all-recvd)\n    (ready-fn)\n\n    (component/send-msgs cmp msgs-to-send)\n\n    (tp/w-timeout\n      5000\n      (go\n        (testing \"all messages received\"\n          (is (= [:test/done] (<! all-recvd))))\n        (testing \"processes more than 1K messages per second\"\n          (let [msgs-per-sec (int (* (/ 1000 (- (component/now) start-ts)) cnt))]\n            (log/debug \"Msgs/s:\" msgs-per-sec)\n            (is (> msgs-per-sec 1000))))\n        (testing \"sent messages equal received messages\"\n          (is (= res @state)))))))\n\n(deftest cmp-all-msgs-handler-cmp-state1 (cmp-all-msgs-handler-cmp-state-fn))\n\n(deftest cmp-handlers-test\n  \"Tests that a) specific handlers receive only their respective messages, b) unhandled-handler receives only\n  those that aren't handled specifically, and c) all-msgs-handler receives all.\n  For this, I use integers once again, and partition the message types by using n mod 10 and n mod 100. For each number\n  whose remainder is zero for one of these, I use a specific message type, and a generic one for all others. Then in\n  the component state atom, it is easily verifiable that all items under a key match the expectations.\"\n  (let [msgs-recvd (atom {:all        []\n                          :div-by-10  []\n                          :div-by-100 []\n                          :unhandled  []})\n        cnt 1000\n        msgs-to-send (vec (range cnt))\n        div-by-10? #(zero? (mod % 10))\n        div-by-100? #(zero? (mod % 100))\n        all-recvd (promise-chan)\n        all-msgs-handler (fn [{:keys [msg-payload current-state put-fn]}]\n                           (let [new-state (update-in current-state [:all] conj msg-payload)]\n                             (when (= cnt (count (:all new-state)))\n                               (put-fn [:test/done]))\n                             {:new-state new-state}))\n        msg-handler (fn [k] (fn [{:keys [msg-payload current-state]}]\n                              {:new-state (update-in current-state [k] conj msg-payload)}))\n        cmp (component/make-component {:state-fn          (fn [_put-fn] {:state msgs-recvd})\n                                       :handler-map       {:int/div-by-10  (msg-handler :div-by-10)\n                                                           :int/div-by-100 (msg-handler :div-by-100)}\n                                       :unhandled-handler (msg-handler :unhandled)\n                                       :all-msgs-handler  all-msgs-handler})\n        ready-fn (:system-ready-fn cmp)]\n    (tap (:out-mult cmp) all-recvd)\n    (ready-fn)\n\n    (component/send-msgs cmp (map (fn [m]\n                                    (let [msg-type (cond (div-by-100? m) :int/div-by-100\n                                                         (div-by-10? m) :int/div-by-10\n                                                         :else :some/type)]\n                                      [msg-type m]))\n                                  msgs-to-send))\n    (tp/w-timeout\n      5000\n      (go\n        (testing \"all messages received\"\n          (is (= [:test/done] (<! all-recvd))))\n        (testing \"sent messages equal received messages\"\n          (is (= msgs-to-send (:all @msgs-recvd))))\n        (testing \"specific handlers only received their respective messages\"\n          (is (every? div-by-10? (:div-by-10 @msgs-recvd)))\n          (is (every? div-by-100? (:div-by-100 @msgs-recvd))))\n        (testing \"unhandled handler did not receive any messages for which specific handler exists\"\n          (let [unhandled-items (:unhandled @msgs-recvd)]\n            (is (every? #(not (div-by-10? %)) unhandled-items))\n            (is (every? #(not (div-by-100? %)) unhandled-items))))))))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/handler_utils_test.cljc",
    "content": "(ns matthiasn.systems-toolbox.handler-utils-test\n  \"Test that handler utils work as expected.\"\n  (:require [matthiasn.systems-toolbox.test-spec]\n            [matthiasn.systems-toolbox.handler-utils :as hu]\n   #?(:clj  [clojure.test :refer [deftest testing is]]\n      :cljs [cljs.test :refer-macros [deftest testing is]])))\n\n(deftest assoc-in-cmp-test\n  (testing \"state map is updated as expected\"\n    (let [handler (hu/assoc-in-cmp [:test :a])]\n      (is (= {:new-state {:test {:a 2}}}\n             (handler {:current-state {:test {}}\n                       :msg-payload 2}))))))\n\n(deftest update-in-cmp-test\n  (testing \"state map is updated as expected\"\n    (let [handler (hu/update-in-cmp [:test :a] inc)]\n      (is (= {:new-state {:test {:a 2}}}\n             (handler {:current-state {:test {:a 1}}}))))))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/perf_runner.cljs",
    "content": "(ns matthiasn.systems-toolbox.perf-runner\n  (:require [doo.runner :refer-macros [doo-tests]]\n            [matthiasn.systems-toolbox.runtime-perf-test]))\n\n(doo-tests 'matthiasn.systems-toolbox.runtime-perf-test)\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/runner.cljs",
    "content": "(ns matthiasn.systems-toolbox.runner\n  (:require [doo.runner :refer-macros [doo-tests]]\n            [matthiasn.systems-toolbox.component-test]\n            [matthiasn.systems-toolbox.system-test]\n            [matthiasn.systems-toolbox.scheduler-test]\n            [matthiasn.systems-toolbox.log :as l]))\n\n;(l/enable-debug-log!)\n\n(doo-tests 'matthiasn.systems-toolbox.component-test\n           'matthiasn.systems-toolbox.system-test\n           'matthiasn.systems-toolbox.scheduler-test)\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/scheduler_test.cljc",
    "content": "(ns matthiasn.systems-toolbox.scheduler-test\n\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require  [matthiasn.systems-toolbox.test-spec]\n    #?(:clj  [clojure.test :refer [deftest testing is]]\n       :cljs [cljs.test :refer-macros [deftest testing is]])\n    #?(:clj  [clojure.core.async :refer [<! put! go promise-chan]]\n       :cljs [cljs.core.async :refer [<! put! promise-chan]])\n             [matthiasn.systems-toolbox.scheduler :as scheduler]\n             [matthiasn.systems-toolbox.system :as system]\n             [matthiasn.systems-toolbox.switchboard :as switchboard]\n             [matthiasn.systems-toolbox.test-promise :as tp]))\n\n;; Add a scheduler to our system and wire it to pong component\n(deftest scheduler-cycle\n  (let [all-recvd (promise-chan)\n        ping-state (atom {:n 0 :expected-cnt 100 :all-recvd all-recvd})\n        pong-state (atom {:n 0})\n        echo-switchboard (system/create ping-state pong-state)\n        spy (promise-chan)\n        stopped #(put! spy true)]\n    (switchboard/send-mult-cmd\n      echo-switchboard\n      [[:cmd/init-comp (scheduler/cmp-map :test/scheduler-cmp)]\n       [:cmd/route {:from :test/scheduler-cmp :to :test/pong-cmp}]\n       ])\n    ;; Scheduling\n    (switchboard/send-cmd\n      echo-switchboard\n      [:cmd/send {:to  :test/scheduler-cmp\n                  :msg [:cmd/schedule-new\n                        {:timeout 1\n                         :id      :cycle\n                         :message [:cmd/ping]\n                         :repeat  true}]}])\n    ;; Scheduling spins ok\n    (tp/w-timeout 1000 (go\n                         (testing \"all messages received\"\n                           (is (true? (<! all-recvd))))\n                         (testing \"sent messages equal received messages\"\n                           (is (>= (:n @pong-state) 100)))))\n\n    (switchboard/send-mult-cmd\n      echo-switchboard\n      ;; Listen for scheduler being stopped\n      [[:cmd/init-comp (system/spy-cmp-map :test/spy-cmp [:info/deleted-timer] stopped)]\n       [:cmd/route {:from :test/scheduler-cmp :to :test/spy-cmp}]\n       ;; Stop the scheduler\n       [:cmd/send {:to  :test/scheduler-cmp\n                   :msg [:cmd/schedule-delete {:id :cycle}]}]])\n\n    (tp/w-timeout 1000 (go (is (true? (<! spy)))))))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/switchboard_observe_tests.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard-observe-tests\n  \"Here, we test the route and route-all wiring between components.\"\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require [matthiasn.systems-toolbox.test-spec]\n    #?(:clj\n            [clojure.test :refer [deftest testing is]]\n       :cljs [cljs.test :refer-macros [deftest testing is]])\n    #?(:clj\n            [clojure.core.async :refer [go]])\n            [matthiasn.systems-toolbox.switchboard :as sb]\n            [matthiasn.systems-toolbox.test-promise :as tp]))\n\n\n(defn observed-cmp-map\n  \"Map for component that receives messages and changes the provided state by calculating sum of accumulated\n  value in state and incoming messages. This allows for checking if all messages were received.\"\n  [cmp-id state-atom]\n  {:cmp-id      cmp-id\n   :handler-map {:test/sum (fn [{:keys [current-state msg-payload]}]\n                             {:new-state (update-in current-state [:sum] + (:n msg-payload))})}\n   :state-fn    (fn [_put-fn] {:state state-atom})})\n\n(defn observing-cmp-map\n  \"Map for component that observes the state of another component.\"\n  [cmp-id observed-atom]\n  {:cmp-id           cmp-id\n   :state-fn         (fn [_put-fn] {:state    (atom {})\n                                    :observed observed-atom})\n   :all-msgs-handler (fn [{:keys [msg]}]\n                       {:emit-msg msg})})\n\n\n;; Tests for components observing the state of another component, as facilitated by :cmd/observe-state.\n\n(deftest observe-1-1-test\n  \"1 observed component, 1 observing component. Test that observing component receives last published state\n  of the observed component.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        observed-atom (atom {})]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(observed-cmp-map :test/recv-cmp-1 recv-state-1)\n                         (observing-cmp-map :test/obs-cmp-1 observed-atom)}]\n       [:cmd/observe-state {:from :test/recv-cmp-1 :to :test/obs-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to  :test/recv-cmp-1\n                                           :msg [:test/sum {:n n}]}]))\n    (testing \":test/recv-cmp-1 in desired state\"\n      (let [test-pred #(= (:sum %) 5050)]\n        (tp/w-timeout 1000 (go (while (not (test-pred @recv-state-1)))))\n        (is (test-pred @recv-state-1))))\n    (testing \"observed state atom mirrors original\"\n      (let [test-pred #(= @observed-atom @recv-state-1)]\n        (tp/w-timeout 1000 (go (while (not (test-pred)))))\n        (is (test-pred))\n        (is (= (:sum @observed-atom) 5050))))))\n\n\n(deftest observe-1-3-test\n  \"1 observed component, 3 observing component. Test that all receive the observed component's last\n  published state.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        observed-atom-1 (atom {})\n        observed-atom-2 (atom {})\n        observed-atom-3 (atom {})]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(observed-cmp-map :test/recv-cmp-1 recv-state-1)\n                         (observing-cmp-map :test/obs-cmp-1 observed-atom-1)\n                         (observing-cmp-map :test/obs-cmp-2 observed-atom-2)\n                         (observing-cmp-map :test/obs-cmp-3 observed-atom-3)}]\n       [:cmd/observe-state {:from :test/recv-cmp-1\n                            :to   #{:test/obs-cmp-1 :test/obs-cmp-2 :test/obs-cmp-3}}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to  :test/recv-cmp-1\n                                           :msg [:test/sum {:n n}]}]))\n    (testing \":test/recv-cmp-1 in desired state\"\n      (let [test-pred #(= (:sum %) 5050)]\n        (tp/w-timeout 1000 (go (while (not (test-pred @recv-state-1)))))\n        (is (test-pred @recv-state-1))))\n    (testing \"observed state atoms mirror original\"\n      (let [test-pred #(= @observed-atom-1 @observed-atom-2 @observed-atom-3 @recv-state-1)]\n        (tp/w-timeout 1000 (go (while (not (test-pred)))))\n        (is (test-pred))\n        (is (= (:sum @observed-atom-1) 5050))\n        (is (= (:sum @observed-atom-2) 5050))\n        (is (= (:sum @observed-atom-3) 5050))))))\n\n\n(deftest observe-1-1-observed-xform-test\n  \"1 observed component, 1 observing component. Test that observing component applies xform function when\n  receiving last published state of the observed component. Let's say we have an observing component\n  with the habit of exaggerating the :sum by a factor of 2. That's something we can express as an xform\n  function. Then we can test it was actually applied to the snapshot.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        observed-atom (atom {})\n        observed-xform (fn [snapshot] (update-in snapshot [:sum] * 2))]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(observed-cmp-map :test/recv-cmp-1 recv-state-1)\n                         (merge (observing-cmp-map :test/obs-cmp-1 observed-atom) {:observed-xform observed-xform})}]\n       [:cmd/observe-state {:from :test/recv-cmp-1 :to :test/obs-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/recv-cmp-1 :msg [:test/sum {:n n}]}]))\n    (testing \":test/recv-cmp-1 in desired state\"\n      (let [test-pred #(= (:sum %) 5050)]\n        (tp/w-timeout 1000 (go (while (not (test-pred @recv-state-1)))))\n        (is (test-pred @recv-state-1))))\n    (testing \"observed state atom mirrors original\"\n      (let [test-pred #(= (:sum @observed-atom) 10100)]\n        (tp/w-timeout 1000 (go (while (not (test-pred)))))\n        (is (test-pred))))))\n\n\n(deftest observe-1-1-snapshot-xform-test\n  \"1 observed component, 1 observing component. Test that observed component applies snapshot xform before\n  publishing state snapshot. This can for example be useful in cases where the observed component stores\n  secrets or anything else that should not be exposed. Here, let's dissoc a key.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        observed-atom (atom {})\n        snapshot-xform-fn #(dissoc % :all-msg-handler-sum)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(merge (observed-cmp-map :test/recv-cmp-1 recv-state-1) {:snapshot-xform-fn snapshot-xform-fn})\n                         (observing-cmp-map :test/obs-cmp-1 observed-atom)}]\n       [:cmd/observe-state {:from :test/recv-cmp-1 :to :test/obs-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/recv-cmp-1 :msg [:test/sum {:n n}]}]))\n    (testing \":test/recv-cmp-1 in desired state\"\n      (let [test-pred #(= (:sum %) 5050)]\n        (tp/w-timeout 1000 (go (while (not (test-pred @recv-state-1)))))\n        (is (test-pred @recv-state-1))))\n    (testing \"observed state atom mirrors original\"\n      (let [test-pred #(= @observed-atom {:sum 5050 :unhandled-handler-sum 0})]\n        (tp/w-timeout 1000 (go (while (not (test-pred)))))\n        (is (test-pred))\n        (is (nil? (:all-msg-handler-sum @observed-atom)))))))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/switchboard_route_tests.cljc",
    "content": "(ns matthiasn.systems-toolbox.switchboard-route-tests\n  \"Here, we test the route and route-all wiring between components.\"\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require  [matthiasn.systems-toolbox.test-spec]\n    #?(:clj  [clojure.test :refer [deftest testing is]]\n       :cljs [cljs.test :refer-macros [deftest testing is]])\n    #?(:clj  [clojure.core.async :refer [go]])\n             [matthiasn.systems-toolbox.switchboard :as sb]\n             [matthiasn.systems-toolbox.test-promise :as tp]))\n\n(defn sender-cmp-map\n  \"Map for component that receives messages and immediately emits them again.\"\n  [cmp-id]\n  {:cmp-id           cmp-id\n   :all-msgs-handler (fn [{:keys [msg]}] {:emit-msg msg})})\n\n(defn recipient-cmp-map\n  \"Map for component that receives messages and changes the provided state by calculating sum of accumulated\n  value in state and incoming messages. This allows for checking if all messages were received.\"\n  [cmp-id state-atom]\n  {:cmp-id            cmp-id\n   :handler-map       {:test/sum (fn [{:keys [current-state msg-payload]}]\n                                   {:new-state (update-in current-state [:sum] + (:n msg-payload))})}\n   :all-msgs-handler  (fn [{:keys [current-state msg-payload]}]\n                        (when msg-payload\n                          {:new-state (update-in current-state [:all-msg-handler-sum] + (:n msg-payload))}))\n   :unhandled-handler (fn [{:keys [current-state msg-payload]}]\n                        (when msg-payload\n                          {:new-state (update-in current-state [:unhandled-handler-sum] + (:n msg-payload))}))\n   :state-fn          (fn [_put-fn] {:state state-atom})})\n\n\n;; Tests for components connected via :cmd/route. This command connects two components, but only for message\n;; types for which the recipient has a handler. Other message types are not relayed.\n(deftest route-1-1-test\n  \"One sender, one receiver, 100 messages of handled type and 100 messages of unhandled type sent.\n  Expectation: handler receives all, as does all-msgs-handler. Unhandled-handler receives none.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 5050)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)}]\n       [:cmd/route {:from :test/send-cmp-1 :to :test/recv-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (test-pred @recv-state-1)))))\n      (is (test-pred @recv-state-1)))\n    (testing \"only handled messages were routed and received by all-msgs-handler\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 5050)))\n    (testing \"unhandled messages were not routed\"\n      (is (zero? (:unhandled-handler-sum @recv-state-1))))))\n\n(deftest route-2-1-test\n  \"Two senders, one receiver, 100 messages of handled type and 100 messages of unhandled type sent from each sender.\n  Expectation: handler receives all, as does all-msgs-handler. Unhandled-handler receives none.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 10100)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (sender-cmp-map :test/send-cmp-2)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)}]\n       [:cmd/route {:from #{:test/send-cmp-1 :test/send-cmp-2} :to :test/recv-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (test-pred @recv-state-1)))))\n      (is (test-pred @recv-state-1)))\n    (testing \"only handled messages were routed and received by all-msgs-handler\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 10100)))\n    (testing \"unhandled messages were not routed\"\n      (is (zero? (:unhandled-handler-sum @recv-state-1))))))\n\n(deftest route-2-3-test\n  \"Two senders, three receivers, 100 messages of handled type and 100 messages of unhandled type sent from each sender.\n  Expectation for each receiver: handler receives all, as does all-msgs-handler. Unhandled-handler receives none.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        recv-state-2 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        recv-state-3 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 10100)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (sender-cmp-map :test/send-cmp-2)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)\n                         (recipient-cmp-map :test/recv-cmp-2 recv-state-2)\n                         (recipient-cmp-map :test/recv-cmp-3 recv-state-3)}]\n       [:cmd/route {:from #{:test/send-cmp-1 :test/send-cmp-2}\n                    :to   #{:test/recv-cmp-1 :test/recv-cmp-2 :test/recv-cmp-3}}]])\n    (doseq [n (range 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (and (test-pred @recv-state-1)\n                                               (test-pred @recv-state-2)\n                                               (test-pred @recv-state-3))))))\n      (is (test-pred @recv-state-1))\n      (is (test-pred @recv-state-2))\n      (is (test-pred @recv-state-3)))\n    (testing \"only handled messages were routed and received by all-msgs-handler\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 10100))\n      (is (= (:all-msg-handler-sum @recv-state-2) 10100))\n      (is (= (:all-msg-handler-sum @recv-state-3) 10100)))\n    (testing \"unhandled messages were not routed and thus not received\"\n      (is (zero? (:unhandled-handler-sum @recv-state-1)))\n      (is (zero? (:unhandled-handler-sum @recv-state-2)))\n      (is (zero? (:unhandled-handler-sum @recv-state-3))))))\n\n\n;; Tests for components connected via :cmd/routeall. This command connects two components, no matter if the recipient\n;; has a handler defined for a particular message type. In case there's no handler defined, the unhandled-handler\n;; will receive the message. The all-msgs-handler will receive all messages.\n(deftest route-all-1-1-test\n  \"One sender, one receiver, 100 messages of handled type and 100 messages of unhandled type sent.\n  Expectation: handler receives all handled messages, all-msgs-handler receives all.\n  Unhandled-handler receives all messages for which there's no handler.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 5050)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)}]\n       [:cmd/route-all {:from :test/send-cmp-1 :to :test/recv-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (test-pred @recv-state-1)))))\n      (is (test-pred @recv-state-1)))\n    (testing \"all-msgs-handler receives both handled and unhandled messages\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 10100)))\n    (testing \"unhandled messages were routed and received by unhandled-handler\"\n      (is (= (:unhandled-handler-sum @recv-state-1) 5050)))))\n\n(deftest route-2-1-test\n  \"Two senders, one receiver, 100 messages of handled type sent from each sender.\n  Expectation: handler receives all handled message. All-msgs-handler receives all.\n  Unhandled-handler receives all messages for which there's no handler.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 10100)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (sender-cmp-map :test/send-cmp-2)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)}]\n       [:cmd/route-all {:from #{:test/send-cmp-1 :test/send-cmp-2} :to :test/recv-cmp-1}]])\n    (doseq [n (range 1 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (test-pred @recv-state-1)))))\n      (is (test-pred @recv-state-1)))\n    (testing \"all-msgs-handler receives both handled and unhandled messages\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 20200)))\n    (testing \"unhandled messages were routed and received by unhandled-handler\"\n      (is (= (:unhandled-handler-sum @recv-state-1) 10100)))))\n\n(deftest route-all-2-3-test\n  \"Two senders, three receivers, 100 messages of handled type sent from each sender.\n  Expectation for each receiver: handler receives all, as does all-msgs-handler.\n  Unhandled-handler receives none.\"\n  (let [switchboard (sb/component :test/my-switchboard)\n        recv-state-1 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        recv-state-2 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        recv-state-3 (atom {:sum 0 :all-msg-handler-sum 0 :unhandled-handler-sum 0})\n        test-pred #(= (:sum %) 10100)]\n    (sb/send-mult-cmd\n      switchboard\n      [[:cmd/init-comp #{(sender-cmp-map :test/send-cmp-1)\n                         (sender-cmp-map :test/send-cmp-2)\n                         (recipient-cmp-map :test/recv-cmp-1 recv-state-1)\n                         (recipient-cmp-map :test/recv-cmp-2 recv-state-2)\n                         (recipient-cmp-map :test/recv-cmp-3 recv-state-3)}]\n       [:cmd/route {:from #{:test/send-cmp-1 :test/send-cmp-2}\n                    :to   #{:test/recv-cmp-1 :test/recv-cmp-2 :test/recv-cmp-3}}]])\n    (doseq [n (range 101)]\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-1 :msg [:test/sum {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/unhandled {:n n}]}])\n      (sb/send-cmd switchboard [:cmd/send {:to :test/send-cmp-2 :msg [:test/sum {:n n}]}]))\n    (testing \"receives all handled messages\"\n      (tp/w-timeout 10000 (go (while (not (and (test-pred @recv-state-1)\n                                               (test-pred @recv-state-2)\n                                               (test-pred @recv-state-3))))))\n      (is (test-pred @recv-state-1))\n      (is (test-pred @recv-state-2))\n      (is (test-pred @recv-state-3)))\n    (testing \"all-msgs-handler receives both handled and unhandled messages\"\n      (is (= (:all-msg-handler-sum @recv-state-1) 10100))\n      (is (= (:all-msg-handler-sum @recv-state-2) 10100))\n      (is (= (:all-msg-handler-sum @recv-state-3) 10100)))\n    (testing \"unhandled messages were routed and received by unhandled-handler\"\n      (is (zero? (:unhandled-handler-sum @recv-state-1)))\n      (is (zero? (:unhandled-handler-sum @recv-state-2)))\n      (is (zero? (:unhandled-handler-sum @recv-state-3))))))\n\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/system.cljc",
    "content": "(ns matthiasn.systems-toolbox.system\n\n  \"A dummy system for use in tests. Has:\n    * a switchboard\n    * a ping component\n    * a pong component\n    * message payloads are functions; they typically deliver promises\n    * an optional spy component that listens for messages\"\n\n  (:require [matthiasn.systems-toolbox.switchboard :as switchboard]\n    #?(:clj [clojure.tools.logging :as log]\n      :cljs [matthiasn.systems-toolbox.log :as log])\n    #?(:clj  [clojure.core.async :refer [<! put! go promise-chan]]\n       :cljs [cljs.core.async :refer [<! put! promise-chan]])))\n\n;; Ping component\n(defn pong-handler\n  [{:keys [current-state]}]\n  (let [new-state (update-in current-state [:n] inc)]\n    (when (= (:n new-state) (:expected-cnt new-state))\n      (put! (:all-recvd new-state) true))\n    {:new-state (update-in current-state [:n] inc)\n     :emit-msg  []}))\n\n(defn ping-cmp-map [cmp-id cmp-state]\n  {:cmp-id      cmp-id\n   :state-fn    (fn [_] {:state cmp-state})\n   :handler-map {:cmd/pong pong-handler}})\n\n;; Pong component\n(defn ping-handler\n  [{:keys [current-state]}]\n  {:new-state (update-in current-state [:n] inc)\n   :emit-msg [:cmd/pong]})\n\n(defn pong-cmp-map [cmp-id cmp-state]\n  {:cmp-id      cmp-id\n   :state-fn    (fn [_] {:state cmp-state})\n   :handler-map {:cmd/ping ping-handler}})\n\n;; System (= switchboard + components + wiring + routing)\n(defn create [ping-state pong-state]\n  (let [echo-switchboard (switchboard/component :test/my-switchboard)]\n    (switchboard/send-mult-cmd\n      echo-switchboard\n      ;; Init/wire components\n      [[:cmd/init-comp #{(ping-cmp-map :test/ping-cmp ping-state)\n                         (pong-cmp-map :test/pong-cmp pong-state)}]\n       ;; Set up switchboard routes\n       [:cmd/route {:from :test/ping-cmp :to :test/pong-cmp}]\n       [:cmd/route {:from :test/pong-cmp :to :test/ping-cmp}]])\n\n    echo-switchboard))\n\n(defn spy-cmp-map\n  \"Optional spy component\n  - types argument is a list of messages we want to spy\n  - f is called in the handler\"\n  [cmp-id types f]\n  (let [handler (fn [{:keys [msg-payload]}]\n                  (log/debug \"Spy:\" msg-payload)\n                  (f)\n                  {})\n        handler-map (->> types\n                         (map #(vector %1 handler))\n                         (into {}))]\n    {:cmp-id      cmp-id\n     :handler-map handler-map}))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/system_test.cljc",
    "content": "(ns matthiasn.systems-toolbox.system-test\n\n  \"Create a system, send some messages, see them flowing correctly.\"\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require [matthiasn.systems-toolbox.test-spec]\n            [matthiasn.systems-toolbox.system :as system]\n            [matthiasn.systems-toolbox.switchboard :as switchboard]\n   #?(:clj  [clojure.test :refer [deftest testing is]]\n      :cljs [cljs.test :refer-macros [deftest testing is]])\n   #?(:clj  [clojure.core.async :refer [<! put! go promise-chan]]\n      :cljs [cljs.core.async :refer [<! put! promise-chan]])\n            [matthiasn.systems-toolbox.test-promise :as tp]))\n\n(deftest message-flow\n  (let [all-recvd (promise-chan)\n        repetitions 1\n        ping-state (atom {:n 0 :expected-cnt repetitions :all-recvd all-recvd})\n        pong-state (atom {:n 0})\n        echo-switchboard (system/create ping-state pong-state)]\n\n    (dotimes [_ repetitions]\n      (switchboard/send-cmd\n        echo-switchboard\n        [:cmd/send {:to  :test/pong-cmp\n                    :msg [:cmd/ping]}]))\n\n    (tp/w-timeout 1000 (go\n                         (testing \"all messages received\"\n                           (is (true? (<! all-recvd))))\n                         (testing \"sent messages equal received messages\"\n                           (is (= repetitions (:n @ping-state) (:n @pong-state))))))))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/test_promise.cljc",
    "content": "(ns matthiasn.systems-toolbox.test-promise\n\n  \"Provide a promise-like experience for testing.\"\n\n  #?(:cljs (:require-macros [cljs.core.async.macros :refer [go]]))\n  (:require\n   #?(:clj  [clojure.test :refer [is]]\n      :cljs [cljs.test :refer-macros [async is]])\n   #?(:clj  [clojure.core.async :refer [go alts! <!! timeout]]\n      :cljs [cljs.core.async :refer [alts! take! timeout]])))\n\n(defn test-async\n  \"Asynchronous test awaiting ch to produce a value or close. Makes use of cljs.test's facility\n  for async testing.\n  Borrowed from http://stackoverflow.com/questions/30766215/how-do-i-unit-test-clojure-core-async-go-macros\"\n  [ch]\n  #?(:clj (<!! ch)\n     :cljs (async done (take! ch (fn [_] (done))))))\n\n(defn test-within\n  \"Asserts that ch does not close or produce a value within ms. Returns a channel from which the value\n  can be taken. Also borrowed from stackoverflow comment above.\"\n  [ms ch]\n  (go (let [t (timeout ms)\n            [v ch] (alts! [ch t])]\n        (is (not= ch t)\n            (str \"Test should have finished within \" ms \"ms.\"))\n        v)))\n\n(defn w-timeout\n  \"Combines test-async and test-within to provide the deref functionality we expect from a promise.\n  The first argument is the timeout in milliseconds, the second argument should be a go-block (which\n  returns a channel with the return value of the block once completed). Then, in that go block, we can\n  await the promise-chan to be delivered first before making any further assertions.\"\n  [ms ch]\n  (test-async\n    (test-within ms ch)))\n"
  },
  {
    "path": "test/matthiasn/systems_toolbox/test_spec.cljc",
    "content": "(ns matthiasn.systems-toolbox.test-spec\n  (:require\n    #?(:clj  [clojure.spec.alpha :as s]\n       :cljs [cljs.spec.alpha :as s])\n    #?(:clj  [clojure.tools.logging :as l]\n       :cljs [matthiasn.systems-toolbox.log :as l])))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; Test Specs\n(s/def :some/type number?)\n(s/def :cmd/ping nil?)\n(s/def :cmd/pong nil?)\n(s/def :int/div-by-10 #(and (number? %) (zero? (mod % 10))))\n(s/def :int/div-by-100 #(and (number? %) (zero? (mod % 100))))\n\n(s/def :test/n number?)\n(s/def :test/done nil?)\n(s/def :test/sum (s/keys :req-un [:test/n]))\n(s/def :test/unhandled :test/sum)\n"
  }
]