Compare commits

...

276 commits

Author SHA1 Message Date
anatoly
fcdff7577a on to 0.1.23 2025-05-04 16:56:19 -04:00
Anatoly
984979b347
Merge pull request #139 from jwr/fixed-logging
fix parameters of goog.log functions to prevent compiler warnings
2025-05-04 16:48:36 -04:00
Jan Rychter
2f4d1a3581 fix parameters of goog.log functions to prevent compiler warnings 2025-04-30 15:36:35 +09:00
anatoly
2bca9089b8 including clj-kondo fix 2025-01-31 12:58:10 -05:00
Anatoly
656af38802
Merge pull request #138 from NoahTheDuke/nb/fix-clj-kondo-config-3
Fix clj-kondo config, part 3: Remove wrapping atom from clj-kondo hook
2025-01-31 12:56:51 -05:00
Noah Bogart
bfd7adf98f Remove wrapping atom from clj-kondo hook 2025-01-29 14:58:39 -05:00
anatoly
08173bc6a1 0.1.20
includes clj-kondo config fix #137
2024-11-04 10:22:00 -05:00
Anatoly
3fc6c58cd6
Merge pull request #137 from NoahTheDuke/nb/fix-clj-kondo-config-2
Fix clj-kondo config, part 2
2024-11-04 10:19:54 -05:00
Noah Bogart
ccaf99910c clj-kondo: Fix docstrings 2024-10-23 15:12:07 -04:00
Noah Bogart
9c588f68c2 clj-kondo: lint defstate as atom 2024-10-23 14:53:41 -04:00
Anatoly
f16d7004ee
Merge pull request #134 from tolitius/dependabot/maven/org.clojure-clojure-1.11.2
Bump org.clojure:clojure from 1.11.1 to 1.11.2
2024-08-22 21:29:37 -04:00
dependabot[bot]
3caea10121
Bump org.clojure:clojure from 1.11.1 to 1.11.2
Bumps [org.clojure:clojure](https://github.com/clojure/clojure) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/clojure/clojure/blob/master/changes.md)
- [Commits](https://github.com/clojure/clojure/compare/clojure-1.11.1...clojure-1.11.2)

---
updated-dependencies:
- dependency-name: org.clojure:clojure
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-22 02:54:08 +00:00
anatoly
fb09fef496 on to 0.1.19 2024-08-21 22:53:31 -04:00
Anatoly
b89b930ec2
Merge pull request #133 from NoahTheDuke/nb/fix-clj-kondo-config
Update defstate clj-kondo hook to return a var
2024-08-21 22:43:40 -04:00
Noah Bogart
4065f5df36 Update defstate clj-kondo hook to return a var 2024-07-11 11:41:52 -04:00
Anatoly
632977ff41
update the "Structure and Interpretation of Computer Programs" link 2024-07-01 13:45:07 -04:00
anatoly
5275023b38 add deps.edn 2024-03-05 22:49:19 -05:00
anatoly
ad6ca6fb5e up to 0.1.18 2024-03-04 18:43:40 -05:00
Anatoly
eff4c04f21
Merge pull request #130 from rajcspsg/issue_128
Add clojure-kondo for mount.defstate
2024-03-04 18:39:28 -05:00
Rajkumar Natarajan
5df6c941f7 incorporate the review comments for .clj-kondo 2024-03-02 15:15:55 -08:00
Rajkumar Natarajan
098b56d14f Add clojure-kondo for mount.defstate 2023-05-30 13:47:07 -07:00
Anatoly
58e2ded430
[doc]: remove the broken travis ci link 2023-04-15 23:00:17 -04:00
anatoly
8a3fc385a2 update changelog: 0.1.17 2022-12-19 15:20:16 -05:00
anatoly
2d050e9055 fix: swap-states non string rollback
thanks to @egg-juxt for bringing it up
2022-12-19 15:17:38 -05:00
Anatoly
b77f504cfd
Merge pull request #122 from rgkirch/patch-1
Update README.md
2021-11-24 16:23:40 -05:00
rgkirch
5ac52b725b
Update README.md 2021-11-20 16:46:43 -05:00
Anatoly
c85da6149c
docs: update license year 2020-09-10 20:12:54 -04:00
anatoly
6cf5390a44 up cljs-repl version for examples 2020-07-31 20:27:04 -04:00
anatoly
bc3924aedf add yang dep for examples 2020-07-30 16:30:29 -04:00
Anatoly
5d992042e4
Merge pull request #114 from dijonkitchen/patch-1
docs: make wording clearer
2019-12-26 09:39:10 -05:00
JC
83542e56a7
docs: make wording clearer
Uses same order 

Uses semantic linefeed for the section: https://rhodesmill.org/brandon/2012/one-sentence-per-line/
2019-12-25 06:16:53 -08:00
anatoly
232df7a8a3 .travis: force openjdk8 2019-08-20 23:19:55 -04:00
Anatoly
10dbbaa40b
Merge pull request #110 from dgr/tweak-composables
Update `only` and `except` single-arity parameter names.
2019-08-20 23:09:53 -04:00
Dave Roberts
5b26fb6092 Update only and except single-arity parameter names.
The single-arity parameter names for the `only` and `except` functions
did not match the same parameters in the dual-arity versions of the
same functions. This makes both arities use the same name for the same
parameter.
2019-08-13 10:38:22 -05:00
Anatoly
e9f9dfca14
[docs]: add clarification about "load-config" fn 2019-07-09 16:28:22 -04:00
Anatoly
348297ee11
[docs]: (create-conn) rephrase clarification 2019-07-09 10:28:49 -04:00
Anatoly
52831f6b3e
[docs]: add fn "create-conn" clarification 2019-07-09 09:51:44 -04:00
Anatoly
9666d3f372
Merge pull request #109 from cloojure/patch-1
Update differences-from-component.md
2019-04-11 20:12:43 -04:00
Alan Thompson
5564e367e7
Update differences-from-component.md
small typo
2019-04-11 15:36:53 -07:00
anatoly
f65ed6a266 add change log for 0.1.16 2019-01-28 10:43:58 -05:00
anatoly
6dafae195d release 0.1.16 2019-01-28 10:36:02 -05:00
Anatoly
5fab543501
Merge pull request #107 from krajj7/fix_106
prevent reloading of mount.core ns - fix https://github.com/tolitius/mount/issues/106
2019-01-28 10:32:51 -05:00
krajj7
2ba3c60995 synced project.clj version with build.boot 2019-01-26 13:13:28 +01:00
krajj7
fb52f79396 prevent reloading of mount.core ns - fix https://github.com/tolitius/mount/issues/106 2019-01-26 13:13:28 +01:00
anatoly
c5f3e4cdf8 #104: current-state should return Derefable on :cljc 2019-01-04 13:00:53 -05:00
anatoly
76e9a71a13 release 0.1.15 2018-12-04 12:15:38 -05:00
Anatoly
8de6b09989
Merge pull request #101 from arichiardi/silent-new-cljs-warning
Silent *logger* warning in latest ClojureScript
2018-11-03 22:05:14 -04:00
Andrea Richiardi
884a2b2d87
Fix project.clj version to match boot's 2018-11-03 17:10:03 -07:00
Andrea Richiardi
c2687d1b9f
Silent *logger* warning in latest ClojureScript
The latest version is a bit stricter and emits a warning for ear-muffed vars
with no ^:dynamic.
2018-11-03 17:07:15 -07:00
Anatoly
17112646f1
[docs]: <! ed badge tags 2018-11-01 18:16:43 -04:00
Anatoly
d9c4f621cd
[docs]: update a link to Alan J. Perlis quote 2018-10-26 11:48:41 -04:00
anatoly
ed1c9944f6 npm: update package.json version
also remove circle.yml
2018-10-26 08:54:42 -04:00
anatoly
e07d7d6aad onto 0.1.15-SNAPSHOT 2018-10-25 22:36:17 -04:00
Anatoly
686b79e03f
[docs]: change feedback channels format 2018-10-25 22:35:25 -04:00
Anatoly
2ff032c8b3
[docs]: add clojar shileds badge 2018-10-25 22:32:26 -04:00
Anatoly
244d834c46
[docs]: reduce slack icon 2018-10-25 22:23:57 -04:00
Anatoly
0ff5fe106c
[docs]: add travis and release badges 2018-10-25 21:52:27 -04:00
anatoly
bb23747273 switch form circle to travis 2018-10-25 19:48:40 -04:00
anatoly
2d5566ae86 release 0.1.14 2018-10-25 18:35:55 -04:00
anatoly
1acd4feb4c #100 cljs: throw js/Error not just a string 2018-10-25 15:45:41 -04:00
Anatoly
c210cc486c
docs: toc add "Disable Lazy Start" sub section 2018-10-12 14:39:01 -04:00
Anatoly
4f8384763b
docs: toc link to "cljc mode" 2018-10-12 14:33:50 -04:00
anatoly
6e848d1ee4 #99 and #95: add ^{:on-lazy-start :throw} 2018-10-12 14:22:49 -04:00
anatoly
db38d8cacd onto 0.1.14-SNAPSHOT 2018-08-07 23:28:29 -04:00
anatoly
594cc58b71 releae 0.1.13 2018-08-07 23:27:21 -04:00
Anatoly
e956dd4de5
Merge pull request #98 from dijonkitchen/patch-1
[docs]: fix spelling
2018-07-18 18:00:57 -04:00
Jonathan Chen
0a208bd275
Docs: Fix spelling 2018-07-18 14:16:41 -07:00
anatoly
5e0a4c5bfc #97: self hosted 0.1.13-SNAPSHOT 2018-05-06 20:56:57 -04:00
anatoly
562340f4dc #83: [docs] add doc string 2018-04-24 15:48:32 -04:00
anatoly
b3f3f49d64 scoping npm package: @tolitius/mount 2018-04-09 12:07:06 -04:00
Anatoly
a4552fc6ed
Merge pull request #97 from arichiardi/self-host-support
self-host ClojureScript support
2018-04-08 22:56:58 -04:00
Andrea Richiardi
697775ef45 Use more modern clojure and clojurescript in the tests 2018-04-06 22:29:43 -07:00
Andrea Richiardi
75d05e3158 Add package.json for npmjs.com publishing 2018-04-06 22:29:43 -07:00
Andrea Richiardi
dc59f3ec98 Add minimal self-host testing 2018-04-06 22:29:43 -07:00
Andrea Richiardi
1ccff026de Move macrovich macro in separate namespace and tweak
This patch makes sure we use a separate namespace for macrovich and that macros
are correctly loaded. It avoids having warnings under :advanced.  It also puts
vars back where they were before as they look in the right order.
2018-04-06 22:29:43 -07:00
Vadim Platonov
be6e9be246 Works under self-hosted Clojurescript 2018-04-06 21:50:17 -07:00
Anatoly
52e360f3ab
Merge pull request #96 from hipitihop/patch-1
Update differences-from-component.md
2018-04-05 11:30:13 -04:00
Denis Johnson
fdb85e0fe2
Update differences-from-component.md
Removed redundant `is`
2018-04-05 14:04:49 +10:00
anatoly
e247636be6 reformat all with linter 2018-03-15 23:50:49 -04:00
Anatoly
e032a6ef54
[changelog]: formatting 2018-02-10 14:57:58 -05:00
anatoly
dc7dbdd51e update changelog for 0.1.12 2018-02-10 14:56:07 -05:00
anatoly
677812e3ee releasing 0.1.12 2018-02-10 14:53:04 -05:00
Anatoly
ac16b04b68
[change log]: formatting 2018-02-09 09:42:50 -05:00
Anatoly
14100ad648
[doc]: note about exception handling 2017-11-08 00:45:17 -05:00
Anatoly
8bb5e19303 Merge pull request #83 from kenrestivo/master
Add docstring for defstate
2017-08-28 12:48:18 -04:00
Anatoly
fb97606b37 Merge pull request #89 from au-phiware/patch-1
Remove ref to old state alias in `swap-states`.
2017-08-24 09:36:01 -04:00
Corin Lawson
a23def2e2f Remove ref to old state alias in swap-states. 2017-08-24 16:33:02 +10:00
Anatoly
e9e107f4fa [doc]: logging via mount-up 2017-08-17 12:03:43 -04:00
ken restivo
31e0b68918 Add docstring. 2017-05-01 21:38:11 -07:00
anatoly
4d961b56c0 :on-reload :noop should not leave stale references 2017-04-06 23:33:27 -04:00
anatoly
095eb81488 #81: stop-except & start-without to take all states 2017-04-06 17:35:45 -04:00
Anatoly
6ae0255677 [docs]: formatting 2017-04-06 15:34:56 -04:00
anatoly
355b9b3ea3 #63: goog.log/error for logging cljs errors 2017-03-14 00:03:57 -04:00
anatoly
2f51e5049e #63: prn => log 2017-03-13 20:34:15 -04:00
anatoly
f01b0538e1 #63: on failure (down) reports and cleans up vs. just "throw" 2017-03-12 23:23:59 -04:00
anatoly
448375d2dc circle: adding -Xmx 2017-03-06 20:07:36 -05:00
anatoly
a49a7e4d55 [cljs docs]: cljs example with boot png 2017-03-02 11:00:11 -05:00
Anatoly
b873590f0a [cljs docs]: boot cljs-example 2017-03-02 10:57:14 -05:00
Anatoly
2cface9878 Merge pull request #78 from jumarko/master
Fix typo in documentation
2017-02-22 10:12:07 -05:00
Juraj Martinka
2f4ffa47a7 Fix typo in documentation 2017-02-22 08:14:15 +01:00
anatoly
436514d407 removing 0.1.12 builds. master is the new latest 2017-01-22 12:18:02 -05:00
anatoly
404ea70bfb [circle]: 0.1.12-SNAPSHOT builds of off master 2017-01-22 11:59:49 -05:00
Anatoly
891869721c Merge pull request #76 from grammati/ipending
Do not start a DerefableState as a side-effect of printing
2017-01-22 11:33:18 -05:00
Chris Perkins
ce5f5ed90a Do not start a DerefableState as a side-effect of printing
See: b80e1fe4b1/src/clj/clojure/core_print.clj (L422-L446)
2017-01-22 06:01:21 -07:00
anatoly
94d6d0188a boot -u to 2.7.1; boot-logservice to 1.2.0 2016-12-28 23:26:27 -05:00
anatoly
9a62688c2f circle ci link to 0.1.12-SNAPSHOT 2016-12-02 18:16:45 -06:00
anatoly
736eacdf33 0.1.11 release changelog 2016-12-02 18:15:36 -06:00
anatoly
17461b1911 onto 0.1.12-SNAPSHOT 2016-12-02 18:12:05 -06:00
anatoly
8f8df230a9 releasing 0.1.11 2016-12-02 18:09:51 -06:00
anatoly
7fbb433e2f Merge branch '0.1.11' 2016-12-02 18:08:44 -06:00
anatoly
c9945aef5b adding a proto-repl playground 2016-12-02 16:33:49 -06:00
anatoly
f38bc2f0e7 opening find-all-states 2016-12-02 13:44:14 -06:00
anatoly
49591d4368 #69: fixing comments "start" => "stop" 2016-11-28 11:43:57 -05:00
anatoly
62cb9dd516 #69: stop accepts collections of states
vs. an implicit all and varargs
2016-11-28 11:34:32 -05:00
anatoly
e2ef7bba55 #68: swap-states and start-with-states take values
in a form of {:start fn :stop fn}
2016-11-24 01:54:24 -05:00
anatoly
087683968d running-states to return a set 2016-11-21 12:26:26 -05:00
anatoly
f49fceb518 #65: (mount/start #{}) is a noop 2016-11-14 17:25:24 -05:00
anatoly
5ec4017fdb #65: (mount/start #{}) is a noop 2016-11-14 17:25:00 -05:00
anatoly
c56bbfcd40 removing log on state discovery 2016-11-03 22:18:26 -04:00
anatoly
a397ae5cc5 mergin restart listener changes 2016-11-02 19:21:23 -04:00
anatoly
1c7f2ccc34 [docs]: link to the latest logging 2016-11-02 19:19:16 -04:00
anatoly
3218f8afaf vec states for watchers 2016-11-02 17:45:21 -04:00
anatoly
a0f10407ee [restart]: listner => listener 2016-11-02 13:56:50 -04:00
anatoly
c189db9d12 set of keys for restart listener "on-change" 2016-11-02 13:54:59 -04:00
anatoly
7d323e727e new-> restart listener 2016-11-02 11:26:24 -04:00
anatoly
d9ea3a56f0 new-> restart listener 2016-11-02 11:25:44 -04:00
anatoly
9b063504e8 restart listener 2016-11-01 21:25:45 -04:00
anatoly
e9ba02b5a5 Merge branch 'master' into 0.1.11 2016-11-01 21:24:30 -04:00
anatoly
ae6763da8c restart listener 2016-11-01 21:23:09 -04:00
Anatoly
fea36ec2a5 [docs]: adding the logo to readme 2016-09-20 14:03:32 -04:00
anatoly
0cc94ad1d2 adding logo to /doc/img 2016-09-20 13:57:29 -04:00
Anatoly
d9fd79001d Merge pull request #60 from jmarca/master
super minor typo fixes
2016-08-15 14:21:22 -04:00
James E. Marca
8f10b85718 white space at end of line
auto fixed by emacs editing
2016-08-15 10:02:37 -07:00
James E. Marca
1f18cb7067 fix some typos I noticed while reading 2016-08-15 10:00:42 -07:00
Anatoly
147d9c52fb Merge pull request #58 from DogLooksGood/master
readme typo fix
2016-07-25 09:46:07 -04:00
DogLooksGood
09ccb3fb1d readme typo fix 2016-07-25 11:48:22 +08:00
Ryan Fowler
f514782716 readme typo fix 2016-07-21 14:52:18 -04:00
anatoly
3d767f6256 adding (running-states) 2016-07-21 14:52:00 -04:00
anatoly
76fb498968 adding (running-states) 2016-07-21 14:48:49 -04:00
Anatoly
e2c73fdc19 Merge pull request #57 from ryfow/patch-1
readme typo fix
2016-07-18 16:02:21 -04:00
Ryan Fowler
110f9ee666 readme typo fix 2016-07-18 14:58:29 -05:00
Andrea Richiardi
76cae93f81 Add note on ^{:on-reload :noop}
According to the Slack conversation with @tolitius
2016-07-10 00:38:45 -04:00
anatoly
ccfde46678 [docs]: typo in "affected states" 2016-07-10 00:36:35 -04:00
anatoly
6ee07a9901 [docs]: typo in "affected states" 2016-07-10 00:36:09 -04:00
Anatoly
307331a4f2 Merge pull request #56 from arichiardi/patch-1
Add note on ^{:on-reload :noop}
2016-07-05 14:00:54 -04:00
Andrea Richiardi
35655ed752 Add note on ^{:on-reload :noop}
According to the Slack conversation with @tolitius
2016-07-05 10:53:39 -07:00
anatoly
19bed168c8 [circle]: latest boot.sh 2016-06-09 17:22:18 -04:00
anatoly
690f9fc6c8 [circle]: latest boot.sh 2016-06-09 17:22:04 -04:00
Anatoly
736d93abd1 [circle ci]: "0.1.11-SNAPSHOT" to "0.1.11" branch 2016-06-09 14:50:40 -04:00
Anatoly
0acb78e16a [circle ci]: "0.1.11-SNAPSHOT" to "0.1.11" branch 2016-06-09 14:47:46 -04:00
anatoly
82ec3d34aa boot check to 0.1.2 2016-06-09 14:24:12 -04:00
anatoly
7ee66db407 boot check to 0.1.2 2016-06-09 14:23:50 -04:00
anatoly
f4683252df upping dev/test deps 2016-05-31 09:47:43 -04:00
anatoly
b7a1c070bb upping dev/test deps 2016-05-31 09:47:31 -04:00
anatoly
5755b7cc49 "boot-reload" to 0.4.8 2016-05-31 09:27:12 -04:00
anatoly
31f92fd52c temp trace: "mounting.. state name" 2016-05-31 09:26:45 -04:00
anatoly
677aa0cfdc "boot-reload" to 0.4.8 2016-05-31 09:24:14 -04:00
anatoly
e34a164c2e temp trace: "mounting.. state name" 2016-04-26 16:07:00 -04:00
Anatoly
afe96e9aad docs: + multiple systems, - "no longer true" diffs 2016-04-18 14:33:05 -04:00
Mike Konkov
f26da26d80 Readme fix on start-with-states 2016-04-18 14:33:05 -04:00
Anatoly
69a592cec6 docs: + multiple systems, - "no longer true" diffs 2016-04-18 14:29:49 -04:00
Anatoly
7becc38282 [docs]: (inc ©-year) 2016-03-15 23:02:25 -04:00
Anatoly
8920b36597 [docs]: (inc ©-year) 2016-03-15 22:18:52 -04:00
Anatoly
bd2ba3ac6d Merge pull request #52 from wambat/master
Readme fix on start-with-states
2016-03-08 19:41:22 -05:00
Mike Konkov
c3ce0c6401 Readme fix on start-with-states 2016-03-09 00:02:37 +01:00
anatoly
dc089c1aec Merge branch 'master' into 0.1.11-SNAPSHOT 2016-03-04 16:29:42 -05:00
anatoly
78b9a23c71 no start/stop sample logging for started/stopped states 2016-03-04 16:29:20 -05:00
Anatoly
9131cbb17a Merge pull request #51 from ideal-knee/patch-1
Make `:start` value a function call.
2016-02-29 12:07:28 -05:00
Dan Kee
ef4dee6c8a Make :start value a function call.
In the existing example, `(defstate conn :start create-conn)` simply binds `conn` to the function `create-conn`, rather than the result of calling the function.
2016-02-29 09:18:02 -06:00
Anatoly
c001c19eb5 [docs]: circleci link onto 0.1.11-SNAPSHOT 2016-02-28 19:02:48 -05:00
anatoly
f6e6a92055 onto 0.1.11-SNAPSHOT 2016-02-28 18:38:56 -05:00
anatoly
064c00599c updating the changelog for 0.1.10 2016-02-28 18:30:12 -05:00
anatoly
8026a7b69c releasing 0.1.10 2016-02-28 18:21:37 -05:00
anatoly
d5eb56c422 runtime args should init to {} 2016-02-28 00:02:53 -05:00
Anatoly
a1e8e9c8e0 [doc]: moving start/stop order above composing states 2016-02-23 01:33:49 -05:00
Anatoly
f801599f68 #47 [docs]: composing states 2016-02-23 01:24:15 -05:00
Anatoly
926e4a9d2a #47 composition tests are in 2016-02-23 00:18:57 -05:00
Anatoly
c2828cc3dc [docs]: fixing a link to a server config example 2016-02-22 23:28:52 -05:00
Anatoly
44d57734b7 [docs]: fixing a link to a server config example 2016-02-22 22:13:25 -05:00
anatoly
977806626d #47 swap-states tests are in. composability tests ...next 2016-02-22 17:26:07 -05:00
anatoly
b77bef3457 #47 except, with-args, only, swap tests.. more to come 2016-02-22 16:48:05 -05:00
anatoly
98496d63d8 #47 alpha draft for composable states 2016-02-22 12:15:19 -05:00
anatoly
dc91a44f72 #46: removing :suspend and :resume... [done] 2016-02-03 23:20:54 -05:00
Anatoly
4787ae12f5 [component diff]: note about Yurt 2016-01-31 23:58:04 -05:00
Anatoly
4a59fab9ba [component diff]: note about Yurt 2016-01-31 23:54:47 -05:00
anatoly
346a5b7f8c onto 0.1.10-SNAPSHOT 2016-01-31 16:03:34 -05:00
anatoly
71663ba87c [changelog]: updating 0.1.9 release date 2016-01-31 15:54:55 -05:00
anatoly
46b4895d95 [changelog]: updating 0.1.9 release date 2016-01-31 15:50:28 -05:00
anatoly
c9de434aa0 releasing 0.1.9 2016-01-31 15:47:19 -05:00
Anatoly
6f64640ada [#36]: docs for on-reload 2016-01-31 14:31:41 -05:00
anatoly
d8749261df #36: adding ^{:on-reload action}
actions are: :noop :stop :restart

:restart is a default action: i.e. if no :on-reload meta is added
2016-01-31 14:08:49 -05:00
anatoly
92736ba2d0 "system" is now https://github.com/tolitius/yurt 2016-01-31 02:10:30 -05:00
anatoly
64596b2674 [system]: is now detachable. stop and refactor out are.. next 2016-01-29 00:31:31 -05:00
Anatoly
9694b775aa [changelog]: 0.1.9-SNAPSHOT done + current 2016-01-26 23:45:33 -05:00
Anatoly
be4f6d9d56 [docs]: note about stater, link to cljs sample app 2016-01-26 00:01:36 -05:00
anatoly
c10a65dee2 [defstate]: refactoring "magic" into a clean fn 2016-01-24 14:19:12 -05:00
Anatoly
39a01c4727 [docs]: swapping states with values 2016-01-23 22:02:40 -05:00
Anatoly
dc5c89b3e9 [docs]: (mount/in-cljc-mode) +details. thx @jwr 2016-01-21 16:00:22 -05:00
anatoly
8a3ce619e8 adding (mount.core/system) 2016-01-21 00:52:07 -05:00
anatoly
b4a79f7ba3 #45: swapping with values is.. in 2016-01-20 17:13:04 -05:00
Anatoly
1646511179 [docs]: (grammar) depluralizing 2016-01-17 02:40:24 -05:00
Anatoly
0a746f00ad [docs]: cleaning up deleted states 2016-01-17 02:18:39 -05:00
anatoly
1d7322ef98 #42: adding a reason to cleanup-if-dirty 2016-01-17 01:50:41 -05:00
anatoly
c918a11e3a #42: cleanup states that were removed from ns 2016-01-17 01:39:38 -05:00
Anatoly
3875215dc3 [docs]: updating links to "pure tea" sample app 2016-01-14 00:34:48 -05:00
anatoly
e3066fe024 refactoring mount sample app
drinking more "pure tea"
2016-01-14 00:24:51 -05:00
Anatoly
d834c6a2b0 [docs]: incorporating new sample app changes 2016-01-14 00:21:49 -05:00
anatoly
07eef10a77 Merge branch 'master' into 0.1.9-SNAPSHOT 2016-01-11 20:01:02 -05:00
anatoly
5e57e3ae7e opening up "cleanup-if-dirty"
to solve random cljs compile/resolution problems
2016-01-11 20:00:38 -05:00
Anatoly
afb1dd772a Merge pull request #43 from frankhenderson/patch-1
minor copy edits
2016-01-11 19:51:54 -05:00
frankhenderson
8ecfbf613b minor copy edits 2016-01-11 14:20:55 -08:00
Anatoly
c72613a609 Merge pull request #41 from ckampfe/typos
fix a few README typos
2016-01-09 11:48:37 -05:00
Clark Kampfe
c023a3491c fix a few README typos 2016-01-09 10:37:56 -06:00
Anatoly
6115d7a813 [diff from component]: updating thoughts 2016-01-08 16:19:56 -05:00
Anatoly
ac46e9c0f9 Merge pull request #40 from achengs/patch-2
minor copy edit
2016-01-08 15:59:46 -05:00
Anatoly
6fb67f333b Merge pull request #39 from achengs/patch-1
minor edit
2016-01-08 15:59:06 -05:00
Andrew Cheng
d1eab49ad7 minor copy edit 2016-01-08 12:54:25 -08:00
Andrew Cheng
62cff2d0e9 minor edit 2016-01-08 12:47:30 -08:00
Anatoly
b24e7a17fe Merge pull request #38 from yatesco/patch-1
Fix spelling mistake.
2016-01-08 07:09:56 -05:00
Colin Yates
3632ee9601 Fix spelling mistake. 2016-01-08 11:13:28 +00:00
anatoly
38f1dabdf2 refactoring cljs logging to Closure (goog.log) 2016-01-06 22:20:46 -05:00
anatoly
0644b02703 refactoring cljs logging to Closure (goog.log) 2016-01-06 22:20:09 -05:00
Anatoly
627d0d5844 [docs]: config example links to dev/../www 2016-01-06 12:30:03 -05:00
anatoly
7fe4541e90 [docs]: app-config => config (in examples) 2016-01-06 12:19:43 -05:00
Anatoly
600701d38a Merge pull request #37 from runejuhl/master
Updated URLs for blog.ndk.io to working ones
2016-01-06 11:14:52 -05:00
Rune Juhl Jacobsen
9bfd642c9b Updated URLs for blog.ndk.io to working ones 2016-01-06 09:22:46 -06:00
Anatoly
9024ce6427 [docs]: adding packaging link to component diff 2016-01-04 19:48:49 -05:00
Anatoly
07b6a5e970 [changelog]: adding 0.1.8 changes 2016-01-04 14:46:57 -05:00
Anatoly
18a96c6cda [docs]: switching circle lastest to 0.1.9-SNAPSHOT 2016-01-04 14:23:46 -05:00
anatoly
4a0f4eaf10 onto 0.1.9-SNAPSHOT 2016-01-04 14:10:44 -05:00
anatoly
1e05c3942f releasing 0.1.8 2016-01-04 14:09:17 -05:00
anatoly
3b39c3b017 Merge branch '0.1.8' 2016-01-04 14:04:30 -05:00
anatoly
d2b14d0cb5 boot-check to 0.1.1 + fixing what it caught 2016-01-04 12:22:52 -05:00
Anatoly
0bb137d53f #35 [docs]: lifecycle funs take vars (not ns) 2016-01-04 09:16:38 -05:00
Anatoly
a896197316 #35 [docs]: lifecycle funs take vars (not ns) 2016-01-03 16:49:47 -05:00
anatoly
ef8227d3ba adding eastwood from boot-check 2016-01-02 14:29:19 -05:00
anatoly
4661534411 pluging in boot-check 2015-12-30 20:37:43 -05:00
anatoly
e8ab0b81e1 Merge branch '0.1.8' 2015-12-30 01:06:51 -05:00
anatoly
5b6df4454c [test-cljs]: require mount to avoid clj <=> cljs require order problems 2015-12-30 01:06:24 -05:00
Anatoly
f9f14ee8e8 [docs]: note and visual about cljs ns reload 2015-12-30 00:59:32 -05:00
anatoly
a0d312fbd4 refactoring reader conditionals out of cljs exceptions macro (thx @DomKM) 2015-12-30 00:55:39 -05:00
anatoly
64a91625aa [cljs example app]: adding a stop states timeout back 2015-12-29 16:30:11 -05:00
anatoly
8cedfaa60a #25, #26 [fix for cljs]: cleaning states inside the macro body 2015-12-29 16:20:33 -05:00
anatoly
0622b64941 Merge branch 'master' into 0.1.8 2015-12-29 14:09:52 -05:00
anatoly
fb12c30245 state: def => defonce 2015-12-29 14:09:22 -05:00
Anatoly
e1fa733a05 [docs packaging]: clojure/script 2015-12-28 12:05:54 -05:00
Anatoly
93439ed41a [docs]: packaging 2015-12-28 12:01:11 -05:00
anatoly
7495a9bdeb Merge branch 'master' into 0.1.8 2015-12-27 17:41:18 -05:00
Anatoly
559ba28987 [docs]: formatting 2015-12-27 15:12:13 -05:00
Anatoly
92cab30519 #32: note about a Clojure version 2015-12-27 15:10:22 -05:00
anatoly
23b0f70927 riding on boot-stripper 2015-12-24 17:25:01 -05:00
anatoly
4f218f4feb mount.tools.graph: states-with deps 2015-12-24 13:55:46 -05:00
Anatoly
242080bf2d Merge pull request #31 from malchmih/patch-4
Simplify without-logging-status
2015-12-24 10:34:51 -05:00
Anatoly
6e1e449d27 Merge pull request #30 from malchmih/patch-3
Fix bug in start-with-args
2015-12-24 10:33:18 -05:00
Malchevskiy Misha
76225dd4ca Simplify without-logging-status 2015-12-24 15:41:17 +03:00
Malchevskiy Misha
f250941b12 Fix bug in start-with-args 2015-12-24 14:05:58 +03:00
Anatoly
6591a4731c Merge pull request #29 from paulrd/patch-3
[docs]: 3 more broken links
2015-12-23 11:51:32 -05:00
Paul
fc7b61c406 3 More Broken Links
I think I found the rest! :-)
2015-12-23 12:48:29 -04:00
Anatoly
1c6bd9c12f Merge pull request #28 from paulrd/patch-2
[docs]: broken link
2015-12-23 11:23:29 -05:00
Anatoly
1ea1d28477 Merge pull request #27 from paulrd/patch-1
[docs]: broken link
2015-12-23 11:22:50 -05:00
Paul
9740d23eb7 Broken Link 2015-12-23 12:18:05 -04:00
anatoly
f98df5b2c4 #22, #25, #26: states with no :stop are restarted on ns recompile 2015-12-23 11:04:47 -05:00
Paul
a75c69c3f4 Broken Link 2015-12-23 10:18:07 -04:00
anatoly
c19ef8af9a Merge branch 'master' into 0.1.8
Conflicts:
	README.md
2015-12-23 01:26:53 -05:00
Anatoly
490954decf [docs]: note about #mount slack channel 2015-12-23 01:24:07 -05:00
Anatoly
59663a87a9 [docs]: note about #mount slack channel 2015-12-23 01:16:35 -05:00
anatoly
7eed08db9b slack icon 2015-12-23 00:52:26 -05:00
Anatoly
2b6af3ba9b [docs]: about restarting states on ns reloads 2015-12-23 00:00:23 -05:00
anatoly
2d25fd9b58 #22: restarting a state on ns recompile
vs. just stopping it, which is not well expected from the users
2015-12-22 23:01:11 -05:00
anatoly
4e5e4fba71 [lein]: keeping a version upto date 2015-12-22 14:47:58 -05:00
anatoly
bb45c14db5 Merge branch 'master' into 0.1.8 2015-12-22 14:46:47 -05:00
anatoly
1e1afa94fd [boot]: nulling out "ensure-branch" for publishing 2015-12-22 14:24:56 -05:00
Anatoly
5035552e75 [changelog]: 0.1.7 2015-12-21 21:59:02 -05:00
Anatoly
a8d46cbd3a [docs]: "New York Stock Exchange Maintenance" link 2015-12-21 21:51:41 -05:00
Anatoly
0c0a519a6b [docs]: factoring in the web server 2015-12-21 21:50:30 -05:00
anatoly
a9eb10c414 onto 0.1.8 2015-12-21 20:54:03 -05:00
60 changed files with 1930 additions and 800 deletions

1
.clj-kondo/config.edn Normal file
View file

@ -0,0 +1 @@
{:config-paths ["../resources/clj-kondo.exports/mount/mount/"]}

8
.gitignore vendored
View file

@ -1,7 +1,10 @@
/target
/classes
/checkouts
pom.xml
.cpcache/
.rebel_readline_history
cljs-test-runner-out
node_modules
pom.xml.asc
.repl*
dev/resources/public/js/*
@ -16,3 +19,6 @@ doo-index.html
/.idea
/.lein-repl-history
/.nrepl-history
.cljs_rhino_repl/
out/
.clj-kondo/.cache

26
.travis.yml Normal file
View file

@ -0,0 +1,26 @@
sudo: false
language: java
script:
- boot test
- boot test-cljs
- boot test-cljs-advanced
install:
- mkdir -p ~/bin
- export PATH=~/bin:$PATH
- curl -L https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh -o ~/bin/boot
- chmod +x ~/bin/boot
jdk: openjdk8
env:
matrix:
- BOOT_CLOJURE_VERSION=1.8.0
global:
- JAVA_OPTS="-Xms512m -Xmx2048m"
jdk:
- openjdk8
cache:
directories:
- $HOME/.m2
- $HOME/.boot/cache/bin
- $HOME/.boot/cache/lib
- $HOME/bin

View file

@ -1,3 +1,85 @@
## 0.1.19
###### Wed Aug 21 22:43:40 2024 -0400
* add `clojure-kondo`
## 0.1.17
###### Mon Dec 19 15:17:21 2022 -0500
* 2d050e9 fix: swap-states non string rollback (thanks to [egg-juxt](https://github.com/egg-juxt))
## 0.1.16
###### Mon Jan 28 10:34:26 2019 -0500
* fb52f79 prevent reloading of mount.core ns ([106](https://github.com/tolitius/mount/issues/106))
* c5f3e4c current-state should return Derefable on :cljc ([104](https://github.com/tolitius/mount/issues/104))
* c2687d1 silent `*logger*` warning in latest ClojureScript([101](https://github.com/tolitius/mount/issues/101))
* bb23747 switch form circle to travis
## 0.1.14
###### Thu Oct 25 18:34:22 2018 -0400
* cljs: throw js/Error not just a string ([#100](https://github.com/tolitius/mount/issues/100))
* add ^{:on-lazy-start :throw} ([#95](https://github.com/tolitius/mount/issues/95) and [#99](https://github.com/tolitius/mount/issues/99))
* self hosted ClojureScript support ([#85](https://github.com/tolitius/mount/issues/85) and [#97](https://github.com/tolitius/mount/issues/97))
## 0.1.12
###### Sat Feb 10 14:53:04 2018 -0500
* remove ref to old state alias in `swap-states` ([#89](https://github.com/tolitius/mount/issues/89))
* `:on-reload :noop` should not leave stale references
* `stop-except` & `start-without` to take all states ([#81](https://github.com/tolitius/mount/issues/81))
* goog.log/error for logging cljs errors ([#63](https://github.com/tolitius/mount/issues/63))
* on failure (down) reports and cleans up vs. just "throw" ([#63](https://github.com/tolitius/mount/issues/63))
* do not start a `DerefableState` as a side-effect of printing ([#76](https://github.com/tolitius/mount/issues/76))
## 0.1.11
###### Fri Dec 2 18:09:51 2016 -0600
* opening `(find-all-states)`
* stop accepts a collection of states (in addition to varargs) ([#69](https://github.com/tolitius/mount/issues/69))
* swap-states and start-with-states take values ([#68](https://github.com/tolitius/mount/issues/68))
* adding `(running-states)` that returns a set
* `(mount/start #{})` is a noop ([#65](https://github.com/tolitius/mount/issues/65))
* removing log on state discovery
* adding a restart listener with watchers
## 0.1.10
###### Sun Feb 28 18:20:52 2016 -0500
* runtime args are now initialized to `{}` (rather than to `:no-args`)
* composing states on mount start ([#47](https://github.com/tolitius/mount/issues/47))
* removing `:suspend` and `:resume` ([#46](https://github.com/tolitius/mount/issues/46))
## 0.1.9
###### Sun Jan 31 15:47:19 2016 -0500
* `:on-reload` #{:noop :stop :restart} ([#36](https://github.com/tolitius/mount/issues/36))
* swapping states with values ([#45](https://github.com/tolitius/mount/issues/45))
* `(mount.core/system)` experiment is refactored to [Yurt](https://github.com/tolitius/yurt)
* cleaning up deleted states ([#42](https://github.com/tolitius/mount/issues/42))
* refactoring mount sample app (4 states, stateless fns)
* refactoring cljs logging to Closure (goog.log)
## 0.1.8
###### Mon Jan 4 14:09:17 2016 -0500
* pluging in [boot-check](https://github.com/tolitius/boot-check)
* refactoring reader conditionals out of cljs exceptions macro (thx [@DomKM](https://github.com/DomKM))
* riding on [boot-stripper](https://github.com/tolitius/boot-stripper)
* mount.tools.graph: [states-with deps](https://github.com/tolitius/mount/blob/0.1.8/src/mount/tools/graph.cljc#L20)
* fixing bug in start-with-args (thx [@malchmih](https://github.com/malchmih)) ([#30](https://github.com/tolitius/mount/issues/30))
* states with no :stop are restarted on ns recompile ([#22](https://github.com/tolitius/mount/issues/22)), ([#25](https://github.com/tolitius/mount/issues/25)), ([#26](https://github.com/tolitius/mount/issues/26))
* restarting a state on ns recompile ([#22](https://github.com/tolitius/mount/issues/22))
## 0.1.7
###### Mon Dec 21 20:52:31 2015 -0500
* making mount [boot](https://github.com/boot-clj/boot)'iful
* cljs `:classifier "aot"` is fixed by boot ([#23](https://github.com/tolitius/mount/issues/23))
* refactoring example app: + www
* stopping/cleaning state when its namespace is recompiled ([#22](https://github.com/tolitius/mount/issues/22))
## 0.1.6
###### Thu Dec 10 00:40:18 2015 -0500

31
Makefile Normal file
View file

@ -0,0 +1,31 @@
.PHONY: clean test jar tag outdated install deploy tree repl
clean:
rm -rf target
rm -rf classes
jar: clean test tag
clojure -A:jar
test: clean
clojure -X:test :patterns '[".*"]' # clojure tests
# clojure -Atest-cljs -r ".*test.self.host.*" # clojure script tests
# run "j8; boot test-cljs" until running cljs tests via deps.edn is fixed
outdated:
clojure -M:outdated
tag:
clojure -A:tag
install: jar
clojure -A:install
deploy: jar
clojure -A:deploy
tree:
mvn dependency:tree
repl:
clojure -A:dev -A:repl

677
README.md
View file

@ -1,18 +1,13 @@
> I think that it's _extraordinarily important_ that we in computer science keep fun in computing
_**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-3.html)_
_**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](https://web.mit.edu/6.001/6.037/sicp.pdf)_
# mount
# mount <img src="doc/img/mount-logo.png" width="70px">
[![<! release](https://img.shields.io/badge/dynamic/json.svg?label=release&url=https%3A%2F%2Fclojars.org%2Fmount%2Flatest-version.json&query=version&colorB=blue)](https://github.com/tolitius/mount/releases)
[![<! clojars](https://img.shields.io/clojars/v/mount.svg)](https://clojars.org/mount)
module | branch | status
----------|----------|----------
mount | `master` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/master.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/master)
mount | `0.1.8` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/0.1.8.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/0.1.8)
[![Clojars Project](http://clojars.org/mount/latest-version.svg)](http://clojars.org/mount)
###### _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel <img src="doc/img/slack-icon.png" width="15px"> (or just [open an issue](https://github.com/tolitius/mount/issues))
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Why?](#why)
@ -25,20 +20,29 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
- [Value of Values](#value-of-values)
- [The Importance of Being Reloadable](#the-importance-of-being-reloadable)
- [Start and Stop Order](#start-and-stop-order)
- [Composing States](#composing-states)
- [Start and Stop Parts of Application](#start-and-stop-parts-of-application)
- [Start an Application Without Certain States](#start-an-application-without-certain-states)
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
- [Swapping Alternate Implementations](#swapping-alternate-implementations)
- [Suspending and Resuming](#suspending-and-resuming)
- [Suspendable Lifecycle](#suspendable-lifecycle)
- [Plugging into (reset)](#plugging-into-reset)
- [Suspendable Example Application](#suspendable-example-application)
- [Swapping States with Values](#swapping-states-with-values)
- [Swapping States with States](#swapping-states-with-states)
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
- [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript)
- [cljc mode](#cljc-mode)
- [Disable Lazy Start](#disable-lazy-start)
- [Packaging](#packaging)
- [Affected States](#affected-states)
- [Recompiling Namespaces with Running States](#recompiling-namespaces-with-running-states)
- [:on-reload](#on-reload)
- [Cleaning up Deleted States](#cleaning-up-deleted-states)
- [Logging](#logging)
- [mount-up](#mount-up)
- [Manual AOP](#manual-aop)
- [Exception Handling](#exception-handling)
- [Clojure Version](#clojure-version)
- [Mount and Develop!](#mount-and-develop)
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
- [New York Stock Exchange Maintenance](#new-york-stock-exchange-maintenance)
- [Web and Uberjar](#web-and-uberjar)
- [Runtime Arguments](#runtime-arguments)
- [License](#license)
@ -60,8 +64,8 @@ If Clojure REPL (i.e. `lein repl`, `boot repl`) fired up instantly, the need to
inside the REPL would go away. But at the moment, and for some time in the future, managing state by making it
reloadable within the same REPL session is important to retain all the Clojure superpowers.
Here is a good [breakdown](http://blog.ndk.io/2014/02/25/clojure-bootstrapping.html) on the Clojure REPL
startup time, and it is [not because of JVM](http://blog.ndk.io/2014/02/11/jvm-slow-startup.html).
Here is a good [breakdown](http://blog.ndk.io/clojure-bootstrapping.html) on the Clojure REPL
startup time, and it is [not because of JVM](http://blog.ndk.io/jvm-slow-startup.html).
`mount` is here to preserve all the Clojure superpowers while making _the application state_ enjoyably reloadable.
@ -70,7 +74,7 @@ Pull request away, let's solve this thing!
### Differences from Component
mount is an alternative to the [component](https://github.com/stuartsierra/component) approach with notable [differences](doc/differences-from-component.md#differences-from-component).
`mount` is an alternative to the [component](https://github.com/stuartsierra/component) approach with notable [differences](doc/differences-from-component.md#differences-from-component).
## How
@ -83,15 +87,15 @@ mount is an alternative to the [component](https://github.com/stuartsierra/compo
Creating state is easy:
```clojure
(defstate conn :start create-conn)
(defstate conn :start (create-conn))
```
where the `create-conn` function is defined elsewhere, can be right above it.
where the `create-conn` function creates a connection (for example to a database) and is defined elsewhere, can be right above it.
In case this state needs to be cleaned / destroyed between reloads, there is also `:stop`
```clojure
(defstate conn :start create-conn
(defstate conn :start (create-conn)
:stop (disconnect conn))
```
@ -99,7 +103,7 @@ That is pretty much it. But wait, there is more.. this state is _a top level bei
`required` by other namespaces or in REPL:
```clojure
dev=> (require '[app.nyse :refer [conn]])
dev=> (require '[app.db :refer [conn]])
nil
dev=> conn
#object[datomic.peer.LocalConnection 0x1661a4eb "datomic.peer.LocalConnection@1661a4eb"]
@ -116,6 +120,22 @@ For example let's say an `app` needs a connection above. No problem:
where `above` is an arbitrary namespace that defines the above state / connection.
### Documentation String
As in any definition (i.e. `def`, `defn`) a documentation string can be added to better describe a state:
```clojure
(defstate answer
"answer to the ultimate question of life universe and everything"
:start (+ 1 41))
```
```clojure
(doc answer)
-------------------------
dev/answer
answer to the ultimate question of life universe and everything
```
## Dependencies
If the whole app is one big application context (or `system`), cross dependencies with a solid dependency graph
@ -128,43 +148,45 @@ If a managing state library requires a whole app buy-in, where everything is a b
it is a framework, and dependency graph is usually quite large and complex,
since it has _everything_ (every piece of the application) in it.
But if stateful things are kept lean and low level (i.e. I/O, queues, etc.), dependency graphs are simple
and small, and everything else is just namespaces and functions: the way it should be.
But if stateful things are kept lean and low level (i.e. I/O, queues, threads, connections, etc.), dependency graphs are simple and small, and everything else is just namespaces and functions: the way it should be.
### Talking States
There are of course direct dependecies that `mount` respects:
There are of course direct dependencies that `mount` respects:
```clojure
(ns app.config
(:require [mount.core :refer [defstate]]))
(defstate app-config
(defstate config
:start (load-config "test/resources/config.edn"))
```
this `app-config`, being top level, can be used in other namespaces, including the ones that create states:
this `config`, being top level, can be used in other namespaces, including the ones that create states:
```clojure
(ns app.database
(:require [mount.core :refer [defstate]]
[app.config :refer [app-config]]))
[app.config :refer [config]]))
(defstate conn :start (create-connection app-config))
(defstate conn :start (create-connection config))
```
[here](dev/clj/app/nyse.clj)
is an example of a Datomic connection that "depends" on a similar `app-config`.
[here](dev/clj/app/www.clj#L32) is an example of a web server that "depends" on a similar `config`.
###### _(the example `load-config` function above comes from [cprop](https://github.com/tolitius/cprop), but could of course be a custom function that loads configuration from a file)_
## Value of values
Lifecycle functions start/stop/suspend/resume can take both functions and values. This is "valuable" and also works:
Lifecycle functions start/stop can take both functions and values. This is "valuable" and also works:
```clojure
(defstate answer-to-the-ultimate-question-of-life-the-universe-and-everything :start 42)
```
Besides scalar values, lifecycle functions can take anonymous functions, partial functions, function references, etc.. Here are some examples:
While it would be useful in REPL and for testing, real application states would usually have start / stop logic, in other words, the real lifecycle.
Besides scalar values, lifecycle functions can take anonymous functions, partial functions, function references, etc.. Here are some examples:
```clojure
(defn f [n]
@ -192,12 +214,12 @@ Besides scalar values, lifecycle functions can take anonymous functions, partial
(defstate private-f :start pf)
```
Check out [fun-with-values-test](test/mount/test/fun_with_values.cljc) for more details.
Check out [fun-with-values-test](test/core/mount/test/fun_with_values.cljc) for more details.
## The Importance of Being Reloadable
`mount` has start and stop functions that will walk all the states created with `defstate` and start / stop them
accordingly: i.e. will call their `:start` and `:stop` defined functions. Hence the whole applicatoin state can be reloaded in REPL e.g.:
accordingly: i.e. will call their `:start` and `:stop` defined functions. Hence the whole application state can be reloaded in REPL e.g.:
```
dev=> (require '[mount.core :as mount])
@ -206,9 +228,9 @@ dev=> (mount/stop)
dev=> (mount/start)
```
While it is not always necessary, mount lificycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace),
to make the whole application reloadable with refreshing the app namespaces.
Here is a [dev.clj](dev/dev.clj) as an example, that sums up to:
While it is not always necessary, mount lifecycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace),
to make the whole application reloadable with refreshing the app namespaces.
Here is a [dev.clj](dev/clj/dev.clj) as an example, that sums up to:
```clojure
(defn go []
@ -235,11 +257,11 @@ The "stop" order is simply `(reverse "start order")`:
dev=> (reset)
08:21:39.430 [nREPL-worker-1] DEBUG mount - << stopping.. nrepl
08:21:39.431 [nREPL-worker-1] DEBUG mount - << stopping.. conn
08:21:39.432 [nREPL-worker-1] DEBUG mount - << stopping.. app-config
08:21:39.432 [nREPL-worker-1] DEBUG mount - << stopping.. config
:reloading (app.config app.nyse app.utils.datomic app)
08:21:39.462 [nREPL-worker-1] DEBUG mount - >> starting.. app-config
08:21:39.462 [nREPL-worker-1] DEBUG mount - >> starting.. config
08:21:39.463 [nREPL-worker-1] DEBUG mount - >> starting.. conn
08:21:39.481 [nREPL-worker-1] DEBUG mount - >> starting.. nrepl
:ready
@ -247,21 +269,85 @@ dev=> (reset)
You can see examples of start and stop flows in the [example app](README.md#mount-and-develop).
## Composing States
Besides calling `(mount/start)` there are other useful ways to start an application:
* [starting parts of an application](README.md#start-and-stop-parts-of-application)
* [starting an application without certain states](README.md#start-an-application-without-certain-states)
* [swapping alternate implementations](README.md#swapping-alternate-implementations)
* [passing runtime arguments](README.md#runtime-arguments)
While all of these are great by themselves, sometimes it is really handy to compose these super powers. For example to start an application with _only_ certain states, _swapping_ a couple of them for new values, while passing runtime _arguments_.
### Composer's Toolbox
Each "tool" has a single responsibility and can be composed with other tools in _any_ combination and order.
* `only` will return _only_ states that it is given + exist (seen by mount) in the application
* `except` will return all the states that it is given _except_ a given set
* `swap` will take a map with keys as states and values as their substitute values
* `swap-states` will take a map with keys as states and values with `{:start fn :stop fn}` as their substitute states
* `with-args` will take a map that could later be accessed by `(mount/args)`
All these functions take one or two arguments. If called with two arguments, the first one will be treated as the universe of states to work with. If called with one argument, it will work with _all known_ to mount states.
None of these functions start or stop the application states, they merely serve as transformations from the initial set of states to the one that will later be passed to `(mount/start)`.
### Be Composing
All of the above is much easier to understand by looking at examples:
```clojure
(-> (only #{#'foo/a
#'foo/b
#'foo/c
#'bar/d
#'baz/e})
(except [#'foo/c
#'bar/d])
(with-args {:a 42})
mount/start)
```
This would start off from 5 states, even though the whole application may have many more states available. It would then exclude two states (i.e. `#'foo/c` and `#'bar/d`), then it will pass runtime arguments `{:a 42}`, and finally it will start the remaining three states: `#'foo/a`, `#'foo/b`, `#'baz/e`.
You may notice that `only` takes a set, while `except` takes a vector in this example. This is done intentionally to demonstrate that both these functions can take any collection of states. `set` would make more sense for most cases though.
Here is a more "involved" example:
```clojure
(-> (only #{#'foo/a
#'foo/b
#'foo/c
#'bar/d
#'baz/e})
(with-args {:a 42})
(except [#'foo/c
#'bar/d])
(swap-states {#'foo/a {:start #(create-connection test-conf)
:stop #(disconnect a)}})
(swap {#'baz/e {:datomic {:uri "datomic:mem://composable-mount"}}})
mount/start)
```
This will do the same thing as the previous example plus it would swap `#'foo/a` with alternative `:start` and `:stop` functions and `#'baz/e` with `{:datomic {:uri "datomic:mem://composable-mount"}}` value before starting the application.
## Start and Stop Parts of Application
In REPL or during testing it is often very useful to work with / start / stop _only a part_ of an application, i.e. "only these two states".
`mount` start/stop functions _optionally_ take namespaces to start/stop:
`mount`'s lifecycle functions, i.e. start/stop, can _optionally_ take states as vars (i.e. prefixed with their namespaces):
```clojure
(mount/start #'app.config/app-config #'app.nyse/conn)
(mount/start #'app.config/config #'app.nyse/conn)
...
(mount/stop #'app.config/app-config #'app.nyse/conn)
(mount/stop #'app.config/config #'app.nyse/conn)
```
which will only start/stop `app-config` and `conn` (won't start any other states).
which will _only_ start/stop `config` and `conn` (won't start/stop any other states).
Here is an [example](test/mount/test/parts.cljc) test that uses only two namespaces checking that the third one is not started.
Here is an [example](test/core/mount/test/parts.cljc) test that uses only two namespaces checking that the third one is not started.
## Start an Application Without Certain States
@ -270,46 +356,82 @@ Whether it is in REPL or during testing, it is often useful to start an applicat
The `start-without` function can do just that:
```clojure
(mount/start-without #'app.feeds/feed-listener
(mount/start-without #'app.feeds/feed-listener
#'app/nrepl)
```
which will start an application without starting `feed-listener` and `nrepl` states.
Here is an [example](test/mount/test/start_without.cljc) test that excludes Datomic connection and nREPL from an application on start.
Here is an [example](test/core/mount/test/start_without.cljc) test that excludes Datomic connection and nREPL from an application on start.
## Swapping Alternate Implementations
During testing it is often very useful to mock/stub certain states. For example runnig a test against an in memory database vs. the real one, running with a publisher that publishes to a test core.async channel vs. the real remote queue, etc.
During testing it is often very useful to mock/stub certain states. For example running a test against an in memory database vs. the real one, running with a publisher that publishes to a test core.async channel vs. the real remote queue, etc.
The `start-with` function can do just that:
### Swapping States with Values
The `start-with` function takes values as substitutes.
Say we have a `send-sms` state:
```clojure
(mount/start-with {#'app.nyse/db #'app.test/test-db
#'app.nyse/publisher #'app.test/test-publisher})
(ns app.sms)
;; ...
(defstate send-sms :start (create-sms-sender
(:sms config)))
```
`start-with` takes a map of states with their substitutes. For example `#'app.nyse/db` here is the real deal (remote) DB that is being substituted with `#'app.test/test-db` state, which could be anything, a map, an in memory DB, etc.
When running tests it would be great _not_ to send the real text messages, but rather send them all to a local core.async channel instead:
One thing to note, whenever
```clojure
(let [sms-ch (chan)
send-sms (fn [sms] (go (>! sms-ch sms)))]
(mount/start-with {#'app.sms/send-sms send-sms}) ;; <<<< swapping the "send-sms" state with a test function
;; testing.. checking "sms-ch" channel
(mount/stop))
```
`start-with` takes a map of states with their substitutes. For example `#'app.sms/send-sms` here is the real deal SMS sender that is being substituted with a `send-sms` test function.
### Swapping States with States
The `start-with-states` function takes values in a form of `{:start fn :stop fn}` as substitutes:
```clojure
(mount/start-with-states {#'app.neo/db {:start #(connect test-config)
:stop #(disconnect db)}
#'app.neo/publisher {:start #(create-pub test-config)
:stop #(close-pub publisher)}})
```
`start-with-states` takes a map of states with their substitutes. For example `#'app.nyse/db` here is the real deal (remote) DB that is being
substituted with `#(connect test-config)` function, which could end up being anything, a map, an in memory DB, etc.
The `:stop` functions of substitutes can be anything, and could refer to the original state references. As in the example above: `db` and `publisher`
are real references. They would need to be accessible from the namespace of course, so you might need to `(:require [app.neo :refer [db]])`
in order to use `db` in `:stop #(disconnect db)` example above.
--
One thing to note is whenever
```clojure
(mount/stop)
```
is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
is run after `start-with`/`start-with-states`, it rolls back to an original "state of states", i.e. `#'app.neo/db` is `#'app.neo/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
Here is an [example](test/mount/test/start_with.cljc) test that starts an app with mocking Datomic connection and nREPL.
Here is an [example](test/core/mount/test/start_with_states.cljc) test that starts an app with mocking Datomic connection and nREPL.
## Stop an Application Except Certain States
Calling `(mount/stop)` will stop all the application states. In case everything needs to be stopped _besides certain ones_, it can be done with `(mount/stop-except)`.
Here is an example of restarting the application without bringing down `#'app.www/nyse-app`:
Here is an example of restarting the application without bringing down `#'app.www/nyse-app`:
```clojure
dev=> (mount/start)
14:34:10.813 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
14:34:10.813 [nREPL-worker-0] INFO mount.core - >> starting.. config
14:34:10.814 [nREPL-worker-0] INFO mount.core - >> starting.. conn
14:34:10.814 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
14:34:10.838 [nREPL-worker-0] INFO mount.core - >> starting.. nyse-app
@ -322,12 +444,12 @@ dev=> (mount/stop-except #'app.www/nyse-app)
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. nrepl
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. conn
14:34:47.766 [nREPL-worker-0] INFO app.db - disconnecting from datomic:mem://mount
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. app-config
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. config
:stopped
dev=>
dev=> (mount/start)
14:34:58.673 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
14:34:58.673 [nREPL-worker-0] INFO mount.core - >> starting.. config
14:34:58.674 [nREPL-worker-0] INFO app.config - loading config from test/resources/config.edn
14:34:58.674 [nREPL-worker-0] INFO mount.core - >> starting.. conn
14:34:58.674 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
@ -337,149 +459,255 @@ dev=> (mount/start)
Notice that the `nyse-app` is not started the second time (hence no more accidental `java.net.BindException: Address already in use`). It is already up and running.
## Suspending and Resuming
Besides starting and stopping states can also be suspended and resumed. While this is not needed most of the time, it does comes really handy _when_ this need is there. For example:
* while working in REPL, you only want to truly restart a web server/queue listener/db connection _iff_ something changed, all other times `(mount/stop)` / `(mount/start)` or `(reset)` is called, these states should not be restarted. This might have to do with time to connect / bound ports / connection timeouts, etc..
* when taking an application out of rotation in a data center, and then phasing it back in, it might be handy to still keep it _up_, but suspend all the client / novelty facing components in between.
and some other use cases.
### Suspendable Lifecycle
In additiong to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones:
```clojure
(defstate web-server :start start-server
:resume resume-server
:stop stop-server)
```
`suspend` function is optional. Combining this with [(mount/stop-except)](#stop-an-application-except-certain-states), can result in an interesting restart behavior where everything is restared, but this `web-server` is _resumed_ instead (in this case `#'app.www/nyse-app` is an example of the above `web-server`):
```clojure
dev=> (mount/stop-except #'app.www/nyse-app)
14:44:33.991 [nREPL-worker-1] INFO mount.core - << stopping.. nrepl
14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. conn
14:44:33.992 [nREPL-worker-1] INFO app.db - disconnecting from datomic:mem://mount
14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. app-config
:stopped
dev=>
dev=> (mount/suspend)
14:44:52.467 [nREPL-worker-1] INFO mount.core - >> suspending.. nyse-app
:suspended
dev=>
dev=> (mount/start)
14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. app-config
14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. conn
14:45:00.298 [nREPL-worker-1] INFO app.db - creating a connection to datomic: datomic:mem://mount
14:45:00.315 [nREPL-worker-1] INFO mount.core - >> resuming.. nyse-app
14:45:00.316 [nREPL-worker-1] INFO mount.core - >> starting.. nrepl
:started
```
Notice `>> resuming.. nyse-app`, which in [this case](https://github.com/tolitius/mount/blob/suspendable/test/app/www.clj#L32) just recreates Datomic schema vs. doing that _and_ starting the actual web server.
### Plugging into (reset)
In case `tools.namespace` is used, this lifecycle can be easily hooked up with `dev.clj`:
```clojure
(defn start []
(mount/start))
(defn stop []
(mount/suspend)
(mount/stop-except #'app.www/nyse-app))
(defn reset []
(stop)
(tn/refresh :after 'dev/start))
```
### Suspendable Example Application
An [example application](https://github.com/tolitius/mount/tree/suspendable/test/app) with a suspendable web server and `dev.clj` lives in the `suspendable` branch. You can clone mount and try it out:
```
$ git checkout suspendable
Switched to branch 'suspendable'
```
## Recompiling Namespaces with Running States
At the development time, whenever you "recompile" a namespace, depending on your setup, new versions of its recompiled classes would get reloaded.
Mount will detect when a namespace with states (i.e. with `(defstate ...)`) was reloaded/recompiled,
and will check every state in this namespace whether it was running at the point of recompilation. If it was, _it will restart it_:
In case this namespace included a state reference (i.e. `(defstate ...)`), mount will check if this state is running at the point of recompilation, and if it is, _it will stop it_. Since after the recompilation an old reference to this state will be lost. Mount will also let you know if the state was stopped during recompilation:
* if a state has a `:stop` function, mount will invoke it on the old version of state (i.e. cleanup)
* it will call a "new" `:start` function _after_ this state is recompiled/redefined
Mount won't keep it a secret, it'll tell you about all the states that had to be restarted during namespace reload/recompilation:
<img src="doc/img/ns-recompile.png" width="500px">
The state of course can be started again:
same is true for recompiling and reloading (figwheel, boot-reload, etc.) namespaces in ClojureScript:
<img src="doc/img/cljs-ns-reload.png" width="500px">
Providing a `:stop` function _is_ optional, but in case a state needs to be cleaned between restarts or on a system shutdown,
`:stop` is highly recommended.
### :on-reload
By default a state will be restarted on its redefinition or a namespace recompilation. However it is not always a desired behavior. Sometimes it's ok to have stale references during REPL sessions / development, other times all that is needed is not a "restart", but just a "stop".
This behavior could be controlled with an optional `:on-reload` meta attribute when defining a state.
In case _nothing_ needs to be done to a running state on reload / recompile / redef, set `:on-reload` to `:noop`:
```clojure
dev=> (mount/start #'app.example/nrepl)
INFO app.utils.logging - >> starting.. #'app.example/nrepl
{:started ["#'app.example/nrepl"]}
(defstate ^{:on-reload :noop}
mem-db :start (connect config)
:stop (disconnect mem-db))
```
When a running state needs to be just "stopped" on reload, set `:on-reload` to `:stop`:
```clojure
(defstate ^{:on-reload :stop}
mem-db :start (connect config)
:stop (disconnect mem-db))
```
Again, by default, if no `:on-reload` meta is added, internally it would be set to `:restart`, in which case a running state will be restarted on a redef / a namespace reload.
Note that `^{:on-reload :noop}` will disable stopping or starting the state on namespace recompilation but it will still obey `(mount/start)` / `(mount/stop)` calls. This means that if any of the namespaces with `(mount/start)` / `(mount/stop)` calls are reloaded or these calls are explicitely executed (i.e. somewhere in the `dev` namespace or in an `:after` clause), the state's start/stop functions will still be called.
## Cleaning up Deleted States
Mount will detect when a state was renamed/deleted from a namespace, and will do two things:
* if a state had a `:stop` function, mount will invoke it on the old version of state (i.e. cleanup)
* will remove any knowledge of this state internally
Here is an example:
```clojure
dev=> (defstate won't-be-here-long :start (println "I am starting... ")
:stop (println "I am stopping... "))
#'dev/won't-be-here-long
dev=>
dev=> (mount/start #'dev/won't-be-here-long)
INFO app.utils.logging - >> starting.. #'dev/won't-be-here-long
I am starting...
{:started ["#'dev/won't-be-here-long"]}
dev=>
```
"deleting" it from REPL, and starting all the states:
```clojure
dev=> (ns-unmap 'dev 'won't-be-here-long)
nil
dev=> (mount/start)
"<< stopping.. #'dev/won't-be-here-long (it was deleted)"
I am stopping...
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
{:started ["#'app.conf/config" "#'app.db/conn" "#'app.www/nyse-app" "#'app.example/nrepl"]}
```
Mount detected that `#'dev/won't-be-here-long` was deleted, hence:
```clojure
<< stopping.. #'dev/won't-be-here-long (it was deleted)
```
## `cljc` mode
By default mount states are kept under var references. While it works for Clojure, it falls short in the land of ClojureScript since, especially during an `:advanced` compilation, var names get compressed + ClojureScript does not support reified vars.
To support both Clojure and ClojureScript mount has a `cljc` mode which is well documented in [here](doc/clojurescript.md#managing-state-in-clojurescript), and can be enabled by `(mount/in-cljc-mode)`.
### Disable Lazy Start
When in `cljc` mode, mount states that are not started by `(mount/start a b c)`, or that are not transitive states: i.e. not `:require`d at the time `(mount/start)` is called, will start lazily whenever they are dereferenced:
```clojure
=> (mount/in-cljc-mode)
:cljc
=> (defstate db-connection :start (println "connecting")
:stop (println "disconnecting..."))
=> db-connection
#object[mount.core.DerefableState 0x546b9d51 {:status :pending, :val nil}]
dev=> (mount/running-states)
#{}
dev=> @db-connection ;; db-connection will start here when deref'ed even though it was not started explicitly
connecting
dev=> (mount/running-states)
#{"#'dev/db-connection"}
```
This can be quite handy as it allows certain app states to start lazily.
However there are cases when it is best to fail in case a certain state is deref'ed while it was not yet started. This is possible by marking such states with `^{:on-lazy-start :throw}` metadata:
```clojure
=> (defstate ^{:on-lazy-start :throw} db-connection :start (do (println "connecting") 42)
:stop (println "disconnecting..."))
=> @db-connection ;; this will throw since db connection is deref'ed before it was started
java.lang.RuntimeException: :on-lazy-start is set to :throw i.e. (defstate {:on-lazy-start :throw} #'dev/db-connection...) and #'dev/db-connection state was not explicitly started before it was deref'ed (i.e. @#'dev/db-connection)
=> (mount/start #'dev/db-connection)
connecting
{:started ["#'dev/db-connection"]}
=> @db-connection
42
```
## Packaging
Since `mount` relies on the Clojure/Script Compiler to learn about all the application states, before `mount/start` is called all the namespaces that have `defstate`s need to be compiled.
At the development time this requirement is mostly transparent, since these namespaces are compiled with nREPL, or refreshed with "tools.namespace", etc. But it becomes important when _packaging_ an application or when starting a web application via [lein-ring](https://github.com/weavejester/lein-ring#general-options)'s or [boot-http](https://github.com/pandeiro/boot-http#-i----init-and--c----cleanup)'s `:init` hooks.
Depending on a structure and a kind of an application, this means that these namespaces need to be _`:required`_ prior to a call to `mount/start` when packaging the app as a stand alone JAR or a WAR.
This can be easily done with choosing an application entry point, which could be a web handler namespace with routes or just an arbitrary app namespace (i.e. `my.app`). In this app entry point namespace all other namespaces that have `defstate` would be `:require`d and a call to the `mount/start` function would be defined:
```clojure
(ns my.app
(:require [a]
[b]
[c]
[mount.core :as mount]))
(defn rock-n-roll [] ;; or (defn -main [args].. )
(mount/start))
```
this would ensure that at the time `(rock-n-roll)` is called, all the namespaces with states were compiled (i.e. mount knows about them). `(rock-n-roll)` can be used in/as a -main function or as a web hook such as `:init`.
In practice only a few namespaces need to be `:require`d, since others will be brought in transitively (i.e. by already required namespaces). From the `my.app` example above, say we had namespaces `d`, `e` and `f` that are required by `a`, and `g` and `h` that are required by `b`. They (`d`, `e`, `f`, `g` and `h`) _won't_ need to be required by `my.app`, since `a` and `b` would "bring" them in.
## Affected States
Every time a lifecycle function (start/stop/suspend/resume) is called mount will return all the states that were affected:
Every time a lifecycle function (start/stop) is called mount will return all the states that were affected:
```clojure
dev=> (mount/start)
{:started [#'app.config/app-config
#'app.nyse/conn
#'app/nrepl
#'check.suspend-resume-test/web-server
#'check.suspend-resume-test/q-listener]}
{:started [#'app.config/config
#'app.nyse/conn
#'app/nrepl]}
```
```clojure
dev=> (mount/suspend)
{:suspended [#'check.suspend-resume-test/web-server
#'check.suspend-resume-test/q-listener]}
```
```clojure
dev=> (mount/start)
{:started [#'check.suspend-resume-test/web-server
#'check.suspend-resume-test/q-listener]}
dev=> (mount/stop)
{:stopped [#'app/nrepl
#'app.nyse/conn
#'app.config/config]}
```
An interesting bit here is a vector vs. a set: all the states are returned _in the order they were changed_.
An interesting bit here is a vector vs. a set: all the states are returned _in the order they were affected_.
## Logging
> All the mount examples have `>> starting..` / `<< stopping..` logging messages, but when I develop an application with mount I don't see them.
Valid question. It was a [conscious choice](https://github.com/tolitius/mount/issues/15) not to depend on any particular logging library, since there are few to select from, and this decision is best left to the developer who may choose to use mount.
Valid question. It was a [conscious choice](https://github.com/tolitius/mount/issues/15) not to depend on any particular logging library, since there are few to select from, and this decision is best left to the developer who may choose to use mount.
Since mount is a _library_ it should _not_ bring any dependencies unless its functionality directly depends on them.
> But I still these logging statements in the examples.
> _But I still these logging statements in the examples..._
The way this is done is via an excellent [robert hooke](https://github.com/technomancy/robert-hooke/). Example applications live in `test`, so does the [utility](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/test/app/utils/logging.clj#L44) that adds logging to all the mount's lifecycle functions on start in [dev.clj](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/dev/dev.clj#L21).
### mount-up
One way to do that would be using "[mount-up](https://github.com/tolitius/mount-up)" that "watches mount's ups and downs":
```clojure
=> (require '[mount-up.core :as mu])
=> (mu/on-upndown :info mu/log :before)
=> (mount/start)
INFO mount-up.core - >> starting.. #'boot.user/server
{:started ["#'boot.user/server"]}
=> (mount/stop)
INFO mount-up.core - << stopping.. #'boot.user/server
{:stopped ["#'boot.user/server"]}
```
### Manual AOP
Another, a more manual way, would be to do it via an excellent [robert hooke](https://github.com/technomancy/robert-hooke/). Example applications live in `test`, so does the [utility](test/clj/tapp/utils/logging.clj#L42) that adds logging to all the mount's lifecycle functions on start in [dev.clj](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/dev/dev.clj#L21).
## Exception Handling
One way to handle exceptions on start/stop would be to simply wrap start/stop functions in `try/catch`.
Another way would be to use a custom [mount-up](https://github.com/tolitius/mount-up/blob/master/README.md#wrapping) wrapper.
## Clojure Version
Since mount [supports both](doc/clojurescript.md#managing-state-in-clojurescript) Clojure and ClojureScript, it relies on [Reader Conditionals](http://clojure.org/reader#The%20Reader--Reader%20Conditionals) that were introduced in `Clojure 1.7`. mount's code is not precompiled (i.e. AOT) and distributed in `.cljc` sources, hence it currently requires `Clojure 1.7` and above.
## Mount and Develop!
`mount` comes with an example [app](dev/clj/app)
that has 3 states:
Besides a [a collection](https://github.com/tolitius/stater) of sample mount applications, mount _sources_ come with two sample apps:
* Clojure [app](dev/clj/app)
* ClojureScript [app](doc/clojurescript.md#mounting-that-clojurescript)
You can clone mount, jump into a REPL and start playing with these built in apps.
Below is an example of the Clojure app that comes with mount.
The app has 4 states:
* `config`, loaded from the files and refreshed on each `(reset)`
* `datomic connection` that uses the config to create itself
* `nyse web app` which is a web server with compojure routes (i.e. the actual app)
* `nrepl` that uses config to bind to host/port
### Running New York Stock Exchange
To try it out, clone `mount`, get to REPL and switch to `(dev)`:
To try it out, clone `mount`, get to REPL (`boot repl` or `lein repl`) and switch to `(dev)`:
```clojure
$ lein repl
$ boot repl
user=> (dev)
#object[clojure.lang.Namespace 0xcf1a0cc "dev"]
@ -490,72 +718,123 @@ start/restart/reset everything using `(reset)`:
```clojure
dev=> (reset)
:reloading (app.config app.nyse app.utils.datomic app dev)
15:30:32.412 [nREPL-worker-1] DEBUG mount - >> starting.. app-config
15:30:32.414 [nREPL-worker-1] INFO app.config - loading config from test/resources/config.edn
15:30:32.422 [nREPL-worker-1] DEBUG mount - >> starting.. conn
15:30:32.430 [nREPL-worker-1] INFO app.nyse - conf: {:datomic {:uri datomic:mem://mount}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}}
15:30:32.430 [nREPL-worker-1] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
15:30:32.430 [nREPL-worker-1] DEBUG mount - >> starting.. nrepl
:reloading (mount.tools.macro mount.core app.utils.logging app.conf app.db app.utils.datomic app.nyse app.www app.example dev)
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.conf - loading config from dev/resources/config.edn
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.db - conf: {:datomic {:uri datomic:mem://mount}, :www {:port 4242}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}, :nrepl {:host 0.0.0.0, :port 7878}}
INFO app.db - creating a connection to datomic: datomic:mem://mount
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
dev=>
```
everything is started and can be played with:
```clojure
dev=> (create-nyse-schema)
dev=> (add-order "GOOG" 665.51M 665.59M 100)
dev=> (add-order "GOOG" 665.50M 665.58M 300)
dev=> (add-order conn {:ticker "GOOG" :bid 665.51M :offer 665.59M :qty 100})
dev=> (add-order conn {:ticker "GOOG" :bid 665.50M :offer 665.58M :qty 300})
dev=> (find-orders "GOOG")
dev=> (find-orders conn "GOOG")
({:db/id 17592186045418, :order/symbol "GOOG", :order/bid 665.51M, :order/qty 100, :order/offer 665.59M}
{:db/id 17592186045420, :order/symbol "GOOG", :order/bid 665.50M, :order/qty 300, :order/offer 665.58M})
```
once something is changed in the code, or you just need to reload everything, do `(reset)`:
since there is also a web server running, we can add orders with HTTP POST (from a different terminal window):
```clojure
$ curl -X POST -d "ticker=TSLA&qty=100&bid=232.38&offer=232.43" "http://localhost:4242/nyse/orders"
{"added":{"ticker":"TSLA","qty":"100","bid":"232.38","offer":"232.43"}}
```
```clojure
dev=> (find-orders conn "TSLA")
({:db/id 17592186045422, :order/symbol "TSLA", :order/bid 232.38M, :order/qty 100, :order/offer 232.43M})
```
once something is changed in the code, or you just need to reload everything, do `(reset)`.
_note: a simple `(mount/stop)` / `(mount/start)` will also work, `(reset)` is for "convenience + ns refresh":_
```clojure
dev=> (reset)
15:32:44.342 [nREPL-worker-2] DEBUG mount - << stopping.. nrepl
15:32:44.343 [nREPL-worker-2] DEBUG mount - << stopping.. conn
15:32:44.343 [nREPL-worker-2] INFO app.nyse - disconnecting from datomic:mem://mount
15:32:44.344 [nREPL-worker-2] DEBUG mount - << stopping.. app-config
INFO app.utils.logging - << stopping.. #'app.example/nrepl
INFO app.utils.logging - << stopping.. #'app.www/nyse-app
INFO app.utils.logging - << stopping.. #'app.db/conn
INFO app.db - disconnecting from datomic:mem://mount
INFO app.utils.logging - << stopping.. #'app.conf/config
:reloading (app.config app.nyse app.utils.datomic app dev)
:reloading (app.conf app.db app.nyse app.www app.example dev)
15:32:44.371 [nREPL-worker-2] DEBUG mount - >> starting.. app-config
15:32:44.372 [nREPL-worker-2] INFO app.config - loading config from test/resources/config.edn
15:32:44.380 [nREPL-worker-2] DEBUG mount - >> starting.. conn
15:32:44.382 [nREPL-worker-2] INFO app.nyse - conf: {:datomic {:uri datomic:mem://mount}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}}
15:32:44.382 [nREPL-worker-2] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
15:32:44.387 [nREPL-worker-2] DEBUG mount - >> starting.. nrepl
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.conf - loading config from dev/resources/config.edn
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.db - conf: {:datomic {:uri datomic:mem://mount}, :www {:port 4242}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}, :nrepl {:host 0.0.0.0, :port 7878}}
INFO app.db - creating a connection to datomic: datomic:mem://mount
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
:ready
```
notice that it stopped and started again.
In nyse's connection [:stop](https://github.com/tolitius/mount/blob/a63c725dcb6afd7ebb65f8a767d69ee0826921e8/test/app/nyse.clj#L18)
function database is deleted. Hence after `(reset)` was called the app was brought its starting point: database was created by the
[:start](https://github.com/tolitius/mount/blob/a63c725dcb6afd7ebb65f8a767d69ee0826921e8/test/app/nyse.clj#L11) function,
but no schema again:
In `app.db` connection `:stop` calls a `disconnect` function where a [database is deleted](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/db.clj#L18). Hence after `(reset)` was called the app was brought its starting point: [database was created](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/db.clj#L11) by the
`:start` that calls a `new-connection` function, and db schema is [created](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/www.clj#L26) by `nyse.app`.
But again no orders:
```clojure
dev=> (find-orders "GOOG")
IllegalArgumentExceptionInfo :db.error/not-an-entity Unable to resolve entity: :order/symbol datomic.error/arg (error.clj:57)
dev=> (find-orders conn "GOOG")
()
dev=> (find-orders conn "TSLA")
()
```
hence the app is in its "clean" state, and ready to rock and roll as right after the REPL started:
```clojure
dev=> (create-nyse-schema)
dev=> (find-orders "GOOG")
()
dev=> (add-order conn {:ticker "TSLA" :bid 232.381M :offer 232.436M :qty 250})
dev=> (add-order "AAPL" 111.712M 111.811M 250)
dev=> (find-orders conn "TSLA")
({:db/id 17592186045418, :order/symbol "TSLA", :order/bid 232.381M, :order/qty 250, :order/offer 232.436M})
```
dev=> (find-orders "AAPL")
({:db/id 17592186045418, :order/symbol "AAPL", :order/bid 111.712M, :order/qty 250, :order/offer 111.811M})
### New York Stock Exchange Maintenance
Say we want to leave the exchange functioning, but would like to make sure that no one can hit it from the web. Easy, just stop the web server:
```clojure
dev=> (mount/stop #'app.www/nyse-app)
INFO app.utils.logging - << stopping.. #'app.www/nyse-app
{:stopped ["#'app.www/nyse-app"]}
dev=>
```
```bash
$ curl localhost:4242
curl: (7) Failed to connect to localhost port 4242: Connection refused
```
everything but the web server works as before:
```clojure
dev=> (find-orders conn "TSLA")
({:db/id 17592186045420, :order/symbol "TSLA", :order/bid 232.381M, :order/qty 250, :order/offer 232.436M})
dev=>
```
once we found who `DDoS`ed us on `:4242`, and punished them, we can restart the web server:
```clojure
dev=> (mount/start #'app.www/nyse-app)
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
{:started ["#'app.www/nyse-app"]}
dev=>
```
```clojure
$ curl localhost:4242
welcome to the mount sample app!
```
## Web and Uberjar
@ -582,7 +861,7 @@ The documentation is [here](doc/runtime-arguments.md#passing-runtime-arguments).
## License
Copyright © 2015 tolitius
Copyright © 2020 tolitius
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.

2
boot.properties Normal file
View file

@ -0,0 +1,2 @@
BOOT_VERSION=2.7.1
BOOT_CLOJURE_VERSION=1.10.1

View file

@ -1,38 +1,56 @@
(def +version+ "0.1.7")
(def +version+ "0.1.18")
;; -- since boot is no more, this file will go away
;; -- once deps.edn "learns" how to run all cljs tests
;; j8; boot test
;; j8; boot test-cljs
;; j8; boot test-cljs-advanced
;; j8; boot -v build-jar push-snapshot
;; j8; boot -v build-jar push-release
(set-env!
:source-paths #{"src"}
:dependencies '[;; mount brings _no dependencies_, everything here is for
:resource-paths #{"resources"}
:dependencies '[;; mount brings _NO DEPENDENCIES_, everything here is for
;; mount dev, examples apps and tests
[org.clojure/clojure "1.7.0" :scope "provided"]
[org.clojure/clojurescript "1.7.189" :scope "provided" :classifier "aot"]
[datascript "0.13.3" :scope "provided"]
[compojure "1.4.0" :scope "provided"]
[org.clojure/clojure "1.8.0" :scope "provided"]
[org.clojure/clojurescript "1.7.228" :scope "provided" :classifier "aot"]
[datascript "0.15.0" :scope "provided"]
[compojure "1.5.0" :scope "provided"]
[ring/ring-jetty-adapter "1.1.0" :scope "provided"]
[cheshire "5.5.0" :scope "provided"]
[hiccups "0.3.0" :scope "provided" :exclusions [org.clojure/clojurescript]]
[com.andrewmcveigh/cljs-time "0.3.14" :scope "provided"]
[tolitius/yang "0.1.9" :scope "provided"]
[ch.qos.logback/logback-classic "1.1.3" :scope "provided"]
[org.clojure/tools.logging "0.3.1" :scope "provided"]
[robert/hooke "1.3.0" :scope "provided"]
[org.clojure/tools.namespace "0.2.11" :scope "provided"]
[org.clojure/tools.nrepl "0.2.12" :scope "provided"]
[com.datomic/datomic-free "0.9.5327" :scope "provided" :exclusions [joda-time]]
[com.datomic/datomic-free "0.9.5359" :scope "provided" :exclusions [joda-time]]
;; proto repl for fun and joy
[proto-repl "0.3.1" :scope "provided"]
[proto-repl-charts "0.3.2" :scope "provided"]
;; boot clj
[boot/core "2.5.1" :scope "provided"]
[boot/core "2.7.1" :scope "provided"]
[adzerk/bootlaces "0.1.13" :scope "test"]
[adzerk/boot-logservice "1.0.1" :scope "test"]
[adzerk/boot-test "1.0.6" :scope "test"]
[adzerk/boot-logservice "1.2.0" :scope "test"]
[adzerk/boot-test "1.1.1" :scope "test"]
[tolitius/boot-check "0.1.2" :scope "test"]
;; boot cljs
[adzerk/boot-cljs "1.7.170-3" :scope "test"]
[adzerk/boot-cljs-repl "0.3.0" :scope "test"]
[pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
[com.cemerick/piggieback "0.2.1" :scope "test" :exclusions [org.clojure/clojurescript]]
[adzerk/boot-cljs "1.7.228-1" :scope "test"]
[adzerk/boot-cljs-repl "0.4.0" :scope "test"]
[cider/piggieback "0.3.9" :scope "test" :exclusions [org.clojure/clojurescript]]
[weasel "0.7.0" :scope "test" :exclusions [org.clojure/clojurescript]]
[adzerk/boot-reload "0.4.2" :scope "test"]
[nrepl "0.4.5" :scope "test"]
[pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
[tolitius/boot-stripper "0.1.0-SNAPSHOT" :scope "test"]
[adzerk/boot-reload "0.4.8" :scope "test"]
[crisptrutski/boot-cljs-test "0.2.1-SNAPSHOT" :scope "test"]])
(require '[adzerk.bootlaces :refer :all]
@ -41,6 +59,8 @@
'[adzerk.boot-cljs :refer [cljs]]
'[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]]
'[adzerk.boot-reload :refer [reload]]
'[tolitius.boot-check :as check]
'[tolitius.boot-stripper :refer [strip-deps-attr]]
'[pandeiro.boot-http :refer :all]
'[crisptrutski.boot-cljs-test :as tcs]
'[clojure.tools.logging :as log]
@ -58,7 +78,7 @@
(deftask dev []
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs"))
(alter-var-root #'log/*logger-factory*
(alter-var-root #'log/*logger-factory*
(constantly (log-service/make-factory log4b)))
(apply set-refresh-dirs (get-env :directories))
(load-data-readers!)
@ -74,18 +94,43 @@
(set-env! :source-paths #(conj % "test/core" "test/cljs"))
(set-env! :resource-paths #{"test/resources"})
(comp
(require '[mount.core])
(comp
(strip-deps-attr :attr :classifier :value "aot")
(tcs/test-cljs ;; :optimizations :advanced
:out-file "mount.js")))
(deftask test-cljs-advanced []
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs"))
(set-env! :resource-paths #{"dev/resources"})
(comp
(cljs :optimizations :advanced :ids #{"mount"})))
(deftask cljs-example
(deftask check-sources []
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs" "test/core" "test/clj" "test/cljs"))
;; (load-data-readers!)
(comp
(check/with-bikeshed)
(check/with-eastwood)
;; (check/with-yagni) ;; yagni does not yet support (throws on) "cljc"
(check/with-kibit)))
(deftask cljs-dev
"mount cljs dev example"
[]
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs"))
(set-env! :resource-paths #{"dev/resources"})
(comp
(serve :dir "dev/resources/public/")
(watch)
(reload)
(cljs-repl)
(cljs :optimizations :none :ids #{"mount"})))
(deftask cljs-example
"mount cljs example"
[]
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs"))
@ -98,7 +143,9 @@
(cljs :optimizations :advanced :ids #{"mount"})))
(task-options!
push #(-> (into {} %) (assoc :ensure-branch nil))
tcs/test-cljs {:js-env :phantom}
push {; :ensure-clean nil
:ensure-branch nil}
pom {:project 'mount
:version +version+
:description "managing Clojure and ClojureScript app state since (reset)"

View file

@ -1,15 +0,0 @@
machine:
java:
version: oraclejdk8
dependencies:
pre:
- wget https://github.com/boot-clj/boot-bin/releases/download/2.4.2/boot.sh
- mv boot.sh boot && chmod a+x boot && sudo mv boot /usr/local/bin
test:
override:
- boot test
- boot test-cljs
- boot test-cljs-advanced
- lein test2junit

52
deps.edn Normal file
View file

@ -0,0 +1,52 @@
{:paths ["src" "resources"]
:deps {} ;; deps no deps
:aliases {:dev {:extra-deps {metosin/jsonista {:mvn/version "0.3.8"}
com.datomic/datomic-free {:mvn/version "0.9.5359"
:exclusions [joda-time/joda-time]}
org.clojure/tools.nrepl {:mvn/version "0.2.12"}
org.clojure/tools.namespace {:mvn/version "0.2.11"}
cheshire/cheshire {:mvn/version "5.5.0"}
compojure/compojure {:mvn/version "1.5.0"}
ring/ring-jetty-adapter {:mvn/version "1.1.0"}
robert/hooke {:mvn/version "1.3.0"}
proto-repl/proto-repl {:mvn/version "0.3.1"}
proto-repl-charts/proto-repl-charts {:mvn/version "0.3.2"}
nrepl/nrepl {:mvn/version "0.7.0"}}}
:test {:extra-paths ["test/core" "test/clj" "test/cljs" "test/resources"]
:extra-deps {com.datomic/datomic-free {:mvn/version "0.9.5359"
:exclusions [joda-time/joda-time]}
org.clojure/tools.nrepl {:mvn/version "0.2.12"}
robert/hooke {:mvn/version "1.3.0"}
org.clojure/tools.logging {:mvn/version "1.3.0"}
io.github.cognitect-labs/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git"
:sha "e7660458ce25bc4acb4ccc3e2415aae0a4907198"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}
:test-cljs {:extra-paths ["test/core" "test/cljs" "test/resources"]
:extra-deps {org.clojure/clojure {:mvn/version "1.8.0"}
org.clojure/clojurescript {:mvn/version "1.7.228"}
com.andrewmcveigh/cljs-time {:mvn/version "0.3.14"}
hiccups/hiccups {:mvn/version "0.3.0"}
datascript/datascript {:mvn/version "0.15.0"}
olical/cljs-test-runner {:mvn/version "3.8.1"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:repl {:extra-paths ["dev/clj"]
:extra-deps {cider/cider-nrepl {:mvn/version "0.22.4"}
org.clojure/tools.logging {:mvn/version "1.2.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
:main-opts ["-e" "(require 'dev)(in-ns 'dev)"
"-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"
"-i" "-f" "rebel-readline.main/-main"]}
:outdated {:extra-deps {olical/depot {:mvn/version "2.0.1"}}
:main-opts ["-m" "depot.outdated.main" "-a" "outdated"]}
:tag {:extra-deps {tolitius/tag {:mvn/version "0.1.7"}}
:main-opts ["-m" "tag.core" "tolitius/mount" "managing Clojure and ClojureScript app state since (reset)"]}
:jar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.128"}}
:extra-paths ["target/about"]
:main-opts ["-m" "hf.depstar.jar" "target/mount.jar" "--exclude" "clojure/core/specs/alpha.*"]}
:deploy {:extra-deps {deps-deploy/deps-deploy {:mvn/version "RELEASE"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "target/mount.jar"]}
:install {:extra-deps {deps-deploy/deps-deploy {:mvn/version "RELEASE"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "install" "target/mount.jar"]}}}

View file

@ -5,9 +5,9 @@
(defn load-config [path]
(info "loading config from" path)
(-> path
slurp
(-> path
slurp
edn/read-string))
(defstate config
(defstate config
:start (load-config "dev/resources/config.edn"))

View file

@ -1,4 +1,4 @@
(ns app.db
(ns app.db
(:require [mount.core :refer [defstate]]
[datomic.api :as d]
[clojure.tools.logging :refer [info]]
@ -20,7 +20,7 @@
(defstate conn :start (new-connection config)
:stop (disconnect config conn))
;; datomic schema (staging as an example)
;; datomic schema (staging for an example)
(defn create-schema [conn]
(let [schema [{:db/id #db/id [:db.part/db]
:db/ident :order/symbol
@ -34,7 +34,7 @@
:db/valueType :db.type/bigdec
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id [:db.part/db]
:db/ident :order/qty
:db/valueType :db.type/long

View file

@ -1,20 +1,16 @@
(ns app.nyse
(:require [datomic.api :as d]
[app.db :refer [create-schema] :as db]
[app.utils.datomic :refer [touch]]))
(defn add-order [ticker bid offer qty] ;; can take connection as param
@(d/transact db/conn [{:db/id (d/tempid :db.part/user)
:order/symbol ticker
:order/bid bid
:order/offer offer
:order/qty qty}]))
(defn add-order [conn {:keys [ticker bid offer qty]}]
@(d/transact conn [{:db/id (d/tempid :db.part/user)
:order/symbol ticker
:order/bid bid
:order/offer offer
:order/qty qty}]))
(defn find-orders [ticker] ;; can take connection as param
(defn find-orders [conn ticker]
(let [orders (d/q '[:find ?e :in $ ?ticker
:where [?e :order/symbol ?ticker]]
(d/db db/conn) ticker)]
(touch db/conn orders)))
(defn create-nyse-schema []
(create-schema db/conn))
:where [?e :order/symbol ?ticker]]
(d/db conn) ticker)]
(touch conn orders)))

View file

@ -1,4 +1,4 @@
(ns app.utils.logging ;; << change to your namespace/path
(ns app.utils.logging ;; << change to your namespace/path
(:require [mount.core]
[robert.hooke :refer [add-hook clear-hooks]]
[clojure.string :refer [split]]
@ -6,41 +6,38 @@
(alter-meta! *ns* assoc ::load false)
(defn- f-to-action [f]
(defn- f-to-action [f {:keys [status]}]
(let [fname (-> (str f)
(split #"@")
first)]
(case fname
"mount.core$up" :up
"mount.core$down" :down
"mount.core$sigstop" :suspend
"mount.core$sigcont" :resume
"mount.core$up" (when-not (:started status) :up)
"mount.core$down" (when-not (:stopped status) :down)
:noop)))
(defn whatcha-doing? [{:keys [status suspend]} action]
(defn whatcha-doing? [action]
(case action
:up (if (status :suspended) ">> resuming"
(if-not (status :started) ">> starting"))
:down (if (or (status :started) (status :suspended)) "<< stopping")
:suspend (if (and (status :started) suspend) "<< suspending")
:resume (if (status :suspended) ">> resuming")))
:up ">> starting"
:down "<< stopping"
false))
(defn log-status [f & args]
(let [{:keys [var] :as state} (second args)
action (f-to-action f)]
(when-let [taking-over-the-world (whatcha-doing? state action)]
(info (str taking-over-the-world ".. " var)))
(defn log-status [f & args]
(let [[state-name state] args
action (f-to-action f state)]
(when-let [taking-over-the-world (whatcha-doing? action)]
(info (str taking-over-the-world ".. " state-name)))
(apply f args)))
(defonce lifecycle-fns
#{#'mount.core/up
#'mount.core/down
#'mount.core/sigstop
#'mount.core/sigcont})
#'mount.core/down})
(defn without-logging-status []
(doall (map #(clear-hooks %) lifecycle-fns)))
;; this is the one to use:
(defn with-logging-status []
(without-logging-status)
(doall (map #(add-hook % log-status) lifecycle-fns)))

View file

@ -1,5 +1,6 @@
(ns app.www
(:require [app.nyse :refer [add-order find-orders create-nyse-schema]]
(:require [app.nyse :refer [add-order find-orders]]
[app.db :refer [conn create-schema]]
[app.conf :refer [config]]
[mount.core :refer [defstate]]
[cheshire.core :refer [generate-string]]
@ -11,21 +12,22 @@
(GET "/" [] "welcome to mount sample app!")
(GET "/nyse/orders/:ticker" [ticker]
(generate-string (find-orders ticker)))
(generate-string (find-orders conn ticker)))
(POST "/nyse/orders" [ticker qty bid offer]
(add-order ticker (bigdec bid) (bigdec offer) (Integer/parseInt qty))
(generate-string {:added {:ticker ticker
:qty qty
:bid bid
:offer offer}})))
(POST "/nyse/orders" [ticker qty bid offer]
(let [order {:ticker ticker
:bid (bigdec bid)
:offer (bigdec offer)
:qty (Integer/parseInt qty)}]
(add-order conn order)
(generate-string {:added order}))))
(defn start-nyse [{:keys [www]}]
(create-nyse-schema) ;; creating schema (usually done long before the app is started..)
(defn start-nyse [conn {:keys [www]}] ;; app entry point
(create-schema conn) ;; just an example, usually schema would already be there
(-> (routes mount-example-routes)
(handler/site)
(run-jetty {:join? false
:port (:port www)})))
(defstate nyse-app :start (start-nyse config)
(defstate nyse-app :start (start-nyse conn config)
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point

View file

@ -1,12 +1,13 @@
(ns dev
(:require [clojure.pprint :refer [pprint]]
[clojure.tools.namespace.repl :as tn]
[boot.core :refer [load-data-readers!]]
[mount.core :as mount]
[mount.core :as mount :refer [defstate]]
[mount.tools.graph :refer [states-with-deps]]
[app.utils.logging :refer [with-logging-status]]
[app.www]
[app.db :refer [conn]]
[app.example]
[app.nyse :refer [create-nyse-schema find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
[app.nyse :refer [find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
(defn start []
(with-logging-status)
@ -39,4 +40,11 @@
(tn/refresh :after 'dev/go))
(mount/in-clj-mode)
(defn load-data-readers!
"Refresh *data-readers* with readers from newly acquired dependencies."
[]
(#'clojure.core/load-data-readers)
(set! *data-readers* (.getRawRoot #'*data-readers*)))
(load-data-readers!)

15
dev/clj/proto_play.clj Normal file
View file

@ -0,0 +1,15 @@
(ns proto-play
(:require [mount.tools.graph :as mg]
[proto-repl-charts.graph :as proto]))
(defn mount->proto [graph]
(reduce (fn [g {:keys [name deps]}]
(-> g
(update :nodes conj name)
(update :edges conj (-> deps (conj name) vec))))
{}
graph))
(->> (mg/states-with-deps)
mount->proto
(proto/graph "a proto graph of mount states"))

View file

@ -13,13 +13,13 @@
(defn find-source-logs [db source]
(d/q '{:find [?t ?msg]
:in [$ ?s]
:in [$ ?s]
:where [[?e :source ?s]
[?e :timestamp ?t]
[?e :msg ?msg]]}
@@db source))
(defn find-all-logs [db]
(->> (map :e (d/datoms @@db :aevt :timestamp))
(->> (map :e (d/datoms @@db :aevt :timestamp))
dedupe
(d/pull-many @@db '[:timestamp :source :msg])))

View file

@ -12,7 +12,7 @@
" &#8594; [" (name source) "]: " msg))
(defn show-log []
(.write js/document
(.write js/document
(html [:ul (doall (for [e (find-all-logs log)]
[:li (format-log-event e)]))])))

View file

@ -1,4 +1,4 @@
{:datomic
{:datomic
{:uri "datomic:mem://mount"}
:www {:port 4242}

View file

@ -8,7 +8,7 @@
- [Using States](#using-states)
- [Thanks](#thanks)
In case you need to manage state in ClojureScript using mount, _all_ the mount Clojure features are supported in ClojureScript.
In case you need to manage state in ClojureScript using mount, _all_ the mount Clojure features are supported in ClojureScript.
Which means all the mount Clojure [documentation](../README.md) is the mount ClojureScript documentation.
With a slight change in [_mode_](clojurescript.md#mount-modes) ( no change in _mood_ though, just the _mode_ :)).
@ -16,19 +16,19 @@ With a slight change in [_mode_](clojurescript.md#mount-modes) ( no change in _m
### The "Why"
Since [reader conditionals](http://clojure.org/reader#The%20Reader--Reader%20Conditionals) were added in Clojure 1.7,
it became a lot easier to target both platforms with lots of code reuse. You might have noticed
it became a lot easier to target both platforms with lots of code reuse. You might have noticed
that most of mount code lives in `.cljc` files.
The way mount is designed it "mounts" itself to a solid Clojure [namespace API](http://clojure.org/namespaces),
The way mount is designed it "mounts" itself to a solid Clojure [namespace API](http://clojure.org/namespaces),
and while `.cljc` helps a lot with targeting Clojure and ClojureScript, JavaScript VM is vastly different from JVM.
Since JavaScript mostly tagrets browsers, mobile devices and IoT,
it is quite importand to [compress](https://github.com/clojure/clojurescript/wiki/Advanced-Compilation) the final result.
Since JavaScript mostly tagrets browsers, mobile devices and IoT,
it is quite important to [compress](https://github.com/clojure/clojurescript/wiki/Advanced-Compilation) the final result.
Which means that Clojure namespaces API are not that well supported in ClojureScript, since they get renamed and optimized
during compilation + of course no native namespace support on the JavaScript side
during compilation + of course no native namespace support on the JavaScript side
(but that is somewhat solved with [Google Closure](https://closure-library.googlecode.com/git-history/docs/local_closure_goog_base.js.source.html#line428)).
But. When developing an application in Clojure and ClojureScript, it would only make sense if the API for any library
But. When developing an application in Clojure and ClojureScript, it would only make sense if the API for any library
would be _identical_ for both platforms. It should be transparent for developers whether they use a library in Clojure or ClojureScript.
It is not possible for all libraries (i.e. concurrency, reified Vars, etc.), but we should try to make it possible for most.
@ -42,28 +42,36 @@ Mount has two modes `clj` and `cljc`.
#### Clojure _and_ ClojureScript Mode
`cljc` mode is is not default, but it is easy to switch to it:
`cljc` is not a default mode, but it is easy to switch to:
To switch Mount into this mode do:
```clojure
(mount/in-cljc-mode)
```
this sets mount into the `cljc` mode. In this mode mount supports _both_: Clojure and ClojureScript with one difference
anywhere before a call to `(mount/start)`, usually at the entry point of an app: in the `-main`, web handler, etc.
This sets mount into the `cljc` mode. In this mode mount supports _both_: Clojure and ClojureScript with one difference
from the default `clj` mode:
> all states are "_derefable_"
which means in order to use them, you'd need to `@` it. That's where the difference between two modes end.
which means in order to use them, you'd need to `@` it. That's where the difference between the two modes ends.
Again, `cljc` mode API is _consistent across both_ Clojure and ClojureScript.
While initially it may sound strange, this approach has very nice properties:
* Mentally something that you defer (`@`) is associated with a state behind it
* Mentally something that you deref (`@`) is associated with a state behind it
* The whole system may start lazily without an explicit call `(mount/start)`
* States may have watchers which is just an idea at this point, but it could be quite useful
Now as the theory is laid out...
No need to call `(mount/in-cljc-mode)` on ClojureScript side, it is only called once on the server (Clojure) side.
_note: `(mount/in-cljc-mode)` does not require the code to be `.cljc`, just a geeky name to convey the support for both modes: Clojure and ClojureScript_
Now that the theory is laid out...
### Mounting that ClojureScript
@ -75,14 +83,16 @@ Let's look at the example [ClojureScript app](../dev/cljs/app) that uses mount t
In order to run it, just compile `cljs` (in `:advanced` mode, because why not? :)) with:
```clojure
$ lein do clean, cljsbuild once prod
```
$ boot cljs-example
Started Jetty on http://localhost:3000
nREPL server started on port 64412 on host 127.0.0.1 - nrepl://127.0.0.1:64412
Adding :require adzerk.boot-cljs-repl to mount.cljs.edn...
Compiling ClojureScript...
Compiling "dev/resources/public/js/compiled/mount.js" from ["src" "dev/cljs"]...
Successfully compiled "dev/resources/public/js/compiled/mount.js" in 23.966 seconds.
• mount.js
```
And just open a browser at `file:///[path-to-mount]/mount/dev/resources/public/index.html`:
And just open a browser at [http://localhost:3000](http://localhost:3000):
<img src="img/mount.cljs.example.png" width="700">

View file

@ -22,17 +22,16 @@ The not so hidden benefit is REPL time reloadability that it brings to the table
- [Then why "mount"!?](#then-why-mount)
- [So what are the differences?](#so-what-are-the-differences)
- [Objects vs. Namespaces](#objects-vs-namespaces)
- [Start and Stop Order](#start-and-stop-order)
- [Component requires whole app buy in](#component-requires-whole-app-buy-in)
- [Start and Stop Order](#start-and-stop-order)
- [Refactoring an existing application](#refactoring-an-existing-application)
- [Code navigation](#code-navigation)
- [Objects vs. Namespaces](#objects-vs-namespaces)
- [Starting and stopping parts of an application](#starting-and-stopping-parts-of-an-application)
- [Boilerplate code](#boilerplate-code)
- [Library vs. Framework](#library-vs-framework)
- [What Component does better](#what-component-does-better)
- [Swapping alternate implementations](#swapping-alternate-implementations)
- [Uberjar / Packaging](#uberjar--packaging)
- [Multiple separate systems](#multiple-separate-systems)
- [Multiple separate systems within the same JVM](#multiple-separate-systems-within-the-same-jvm)
- [Visualizing dependency graph](#visualizing-dependency-graph)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -51,32 +50,12 @@ Before moving on to differences, [here](https://news.ycombinator.com/item?id=246
## So what are the differences?
### Objects vs. Namespaces
One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything.
This is how Component "separates explicit dependencies" and "clears the bounaries".
This is also how an Object Oriented language does it, which does not leave a lot of room for functions:
with Component most of the functions are _methods_ which is an important distinction.
Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace
can be easily `:require`d in another.
### Start and Stop Order
Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build
a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph.
Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states
and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays
it back and forth on stop and start.
### Component requires whole app buy in
Component really only works if you build your entire app around its model: application is fully based on Components
where every Component is an Object.
Mount does not require you to "buy anything at all", it is free :) Just create a `defstate` whenever/whereever
Mount does not require you to "buy anything at all", it is free :) Just create a `defstate` whenever/wherever
you need it and use it.
This one was a big deal for all the projects we used Component with, "the whole app buy in" converts an "_open_" application
@ -89,6 +68,15 @@ than to
Again this is mostly a personal preference: the code works in both cases.
### Start and Stop Order
Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build
a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph.
Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states
and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays
it back and forth on stop and start.
### Refactoring an existing application
Since to get the most benefits of Component the approach is "all or nothing", to rewrite an existing application
@ -103,6 +91,17 @@ Component changes the way the code is structured. Depending on the size of the c
Since Mount relies on Clojure namespaces (`:require`/`:use`), navigation across functions / states is exactly
the same with or without Mount: there are no extra mental steps.
### Objects vs. Namespaces
One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything.
This is how Component "separates explicit dependencies" and "clears the boundaries".
This is also how an Object Oriented language does it, which does not leave a lot of room for functions:
with Component most of the functions are _methods_ which is an important distinction.
Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace
can be easily `:require`d in another.
### Starting and stopping _parts_ of an application
Component can't really start and stop parts of an application within the same "system". Other sub systems can be
@ -143,44 +142,27 @@ Mount is pretty much:
no "ceremony".
### Library vs. Framework
Mount uses namespaces and vars where Component uses records and protocols.
Component manages records and protocols,
and in order to do that it requires a whole app buy-in,
which makes it a _framework_.
Mount does not need to manage namespaces and vars,
since it is very well managed by the Clojure Compiler,
which makes it a _library_.
## What Component does better
### Swapping alternate implementations
### Multiple separate systems within the same JVM
This is someting that is very useful for testing and is very easy to do in Component by simply assoc'ing onto a map.
With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which _might_ be useful for testing, i.e. if you need to have `dev db` and `test db` started in the _same_ REPL, to _run tests within the same REPL you develop in_.
Mount can do it to: https://github.com/tolitius/mount#swapping-alternate-implementations
Development workflows vary and tend to be a subjective / preference based more than a true recipe, but I believe it is much cleaner to run tests in the _separate_ REPL / process. Moreover run them continuously: i.e. `boot watch speak test`: this way you don't event need to look at that other REPL / terminal, Boot will _tell_ you whether the tests pass or fail after any file is changed.
The reason it is in "Component does it better" section is because, while result is the same, merging maps is a bit simpler than:
```clojure
(mount/start-with {#'app.nyse/db #'app.test/test-db
#'app.nyse/publisher #'app.test/test-publisher})
```
### Uberjar / Packaging
Since Component fully controls the `system` where the whole application lives, it is quite simple
to start an application from anywhere including a `-main` function of the uberjar.
In order to start the whole system in development, Mount just needs `(mount/start)` or `(reset)`
it's [simple](https://github.com/tolitius/mount#the-importance-of-being-reloadable).
However there is no "tools.namespaces"/REPL at a "stand alone jar runtime" and in order for Mount to start / stop
the app, states need to be `:require`/`:use`d, which is usually done within the same namespace as `-main`.
Depending on app dependencies, it could only require a few states to be `:require`/`:use`d, others
will be brought transitively. Here is an [example](uberjar.md#creating-reloadable-uberjarable-app) of building a wepapp uberjar with Mount.
On the flip side, Component _system_ usually requires lots of `:require`s as well, since in order to be built, it needs to "see" all the top level states.
###### _conclusion: it's simple in Mount as well, but requires an additional step._
### Multiple separate systems
With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which is very useful for testing.
Mount keeps states in namespaces, hence the app becomes "[The One](https://en.wikipedia.org/wiki/Neo_(The_Matrix))", and there can't be "multiples The Ones".
Mount keeps states in namespaces, hence the app becomes "[The One](https://en.wikipedia.org/wiki/Neo_(The_Matrix))", and there can't be "multiples The Ones". In practice, if we are talking about stateful external resources, there is trully only _one_ of them with a given configuration. Different configuration => different state. It's that simple.
Testing is not alien to Mount and it knows how to do a thing or two:
@ -188,11 +170,13 @@ Testing is not alien to Mount and it knows how to do a thing or two:
* [start an application without certain states](https://github.com/tolitius/mount#start-an-application-without-certain-states)
* [swapping alternate implementations](https://github.com/tolitius/mount#swapping-alternate-implementations)
* [stop an application except certain states](https://github.com/tolitius/mount#stop-an-application-except-certain-states)
* [suspending and resuming](https://github.com/tolitius/mount#suspending-and-resuming)
* [composing states](https://github.com/tolitius/mount#composing-states)
But running two apps in the same JVM side by side with "same but different" states, is not something Mount can do at the moment.
After [booting mount](http://www.dotkam.com/2015/12/22/the-story-of-booting-mount/) I was secretly thinking of achieving multiple separate systems by running them in different [Boot Pods](https://github.com/boot-clj/boot/wiki/Pods).
###### _conclusion: needs more thinking._
But the more I think about it, the less it feels like a mount's core functionality. So I created [Yurt](https://github.com/tolitius/yurt) that can easily create and run multiple separate mount systems simultaniously.
###### _conclusion: can be done with mount as well, but via a different dependency._
### Visualizing dependency graph
@ -201,3 +185,31 @@ Having this visualization is really helpful, especially during code discusions b
Mount does not have this at the moment. It does have all the data to create such a visualization, perhaps even
by building a graph out of the data it has just for this purpose.
There is a [`(states-with-deps)`](https://github.com/tolitius/mount/blob/master/src/mount/tools/graph.cljc#L20) function that can help out:
```clojure
dev=> (require '[mount.tools.graph :as graph])
dev=> (graph/states-with-deps)
({:name "#'app.conf/config",
:order 1,
:status #{:started},
:deps #{}}
{:name "#'app.db/conn",
:order 2,
:status #{:started},
:deps #{"#'app.conf/config"}}
{:name "#'app.www/nyse-app",
:order 3,
:status #{:started},
:deps #{"#'app.conf/config"}}
{:name "#'app.example/nrepl",
:order 4,
:status #{:started},
:deps #{"#'app.www/nyse-app" "#'app.conf/config"}})
```
But it does not draw :), and it currently only supports Clojure, not ClojureScript.
###### _conclusion: needs more thinking._

BIN
doc/img/cljs-ns-reload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
doc/img/mount-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 43 KiB

BIN
doc/img/slack-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "@tolitius/mount",
"version": "0.1.16",
"license": "EPL-1.0",
"homepage": "https://github.com/tolitius/mount",
"repository": {
"type": "git",
"url": "https://github.com/tolitius/mount"
},
"author": {
"name": "tolitius",
"url": "http://www.dotkam.com"
},
"files": [
"src/*"
],
"directories": {
"lib": "src"
},
"dependencies": {
"ws": "^8.16.0"
}
}

45
pom.xml Normal file
View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<groupId>mount</groupId>
<artifactId>mount</artifactId>
<version>0.1.23</version>
<name>mount</name>
<description>managing Clojure and ClojureScript app state since (reset)</description>
<url>https://github.com/tolitius/mount</url>
<licenses>
<license>
<name>Eclipse Public License</name>
<url>http://www.eclipse.org/legal/epl-v10.html</url>
</license>
</licenses>
<developers>
<developer>
<name>tolitius</name>
</developer>
</developers>
<scm>
<url>https://github.com/tolitius/mount</url>
<connection>scm:git:git://github.com/tolitius/mount.git</connection>
<developerConnection>scm:git:ssh://git@github.com/tolitius/mount.git</developerConnection>
<tag>HEAD</tag>
</scm>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.11.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
</build>
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
</project>

View file

@ -1,4 +1,4 @@
(defproject mount "0.1.7"
(defproject mount "0.1.17"
:description "managing Clojure and ClojureScript app state since (reset)"
:url "https://github.com/tolitius/mount"
:license {:name "Eclipse Public License"
@ -7,10 +7,13 @@
:source-paths ["src"]
:dependencies [] ;; for visual clarity
:tach {:test-runner-ns 'mount.test-self-host
:source-paths ["test/core"]}
:profiles {:dev {:source-paths ["dev" "dev/clj" "test/clj"]
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]; :classifier "aot"]
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.946"]; :classifier "aot"]
[datascript "0.13.3"]
[compojure "1.4.0"]
[ring/ring-jetty-adapter "1.1.0"]
@ -27,7 +30,8 @@
:plugins [[lein-cljsbuild "1.1.1"]
[lein-doo "0.1.6"]
[lein-figwheel "0.5.0-2"]
[test2junit "1.1.3"]]
[test2junit "1.1.3"]
[lein-tach "1.0.0"]]
:test2junit-output-dir ~(or (System/getenv "CIRCLE_TEST_REPORTS") "target/test2junit")

View file

@ -0,0 +1,3 @@
{:linters {:mount/defstate {:level :warning}}
:hooks {:analyze-call {mount.core/defstate hooks.defstate/defstate
mount.core/defstate! hooks.defstate/defstate}}}

View file

@ -0,0 +1,39 @@
(ns hooks.defstate
(:require [clj-kondo.hooks-api :as api]))
(defn defstate [{:keys [node]}]
(let [[n & args] (next (:children node))
[docs args] (if (string? (api/sexpr (first args)))
[(first args) (next args)]
[nil args])
m (when-let [m (first (:meta n))]
(api/sexpr m))
m (if (map? m) m {})
ks (cond-> (take 1 args)
(> (count args) 2) (conj (nth args 2)))
invalid-key (first (remove (comp (partial contains? #{:start :stop}) api/sexpr) ks))]
(cond
invalid-key
(api/reg-finding!
{:message (str "lifecycle functions can only contain `:start` and `:stop`. illegal function found: " (api/sexpr invalid-key))
:type :mount/defstate
:row (:row (meta invalid-key))
:col (:col (meta invalid-key))})
(not (contains? (set (map api/sexpr ks)) :start))
(throw (ex-info "lifecycle functions must include `:start`" {}))
((complement contains?) #{2 4} (count args))
(throw (ex-info "lifecycle functions must consist of no more than 2 pair forms: `:start` and `:stop`" {}))
(and (contains? m :on-reload) (not (contains? #{:noop :stop} (:on-reload m))))
(api/reg-finding!
{:message "metadata `:on-reload` key can only have value of `noop` or `stop`"
:type :mount/defstate
:row (:row (meta n))
:col (:col (meta n))})
:else
{:node (api/list-node
(cond-> [(api/token-node 'def) n]
docs (conj docs)
true (conj (api/list-node
(list*
(api/token-node 'do)
args)))))})))

View file

@ -1,34 +1,37 @@
(ns mount.core
#?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro])
:cljs (:require [mount.tools.macro :as macro]))
#?(:clj {:clojure.tools.namespace.repl/load false}) ; prevent reloading of this ns
#?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro]
[mount.tools.macrovich :refer [deftime]]
[mount.tools.logger :refer [log]]
[clojure.set :refer [intersection]]
[clojure.string :as s])
:cljs (:require [mount.tools.macro]
[clojure.set :refer [intersection]]
[mount.tools.logger :refer [log]]))
#?(:cljs (:require-macros [mount.core]
[mount.tools.macro :refer [on-error throw-runtime]])))
[mount.tools.macro :refer [on-error throw-runtime]]
[mount.tools.macrovich :refer [deftime]])))
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
(defonce ^:private -args (atom {})) ;; mostly for command line args and external files
(defonce ^:private state-seq (atom 0))
(defonce ^:private mode (atom :clj))
(defonce ^:private meta-state (atom {}))
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
;; supporting tools.namespace: (disable-reload!)
#?(:clj
(alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency
(defn- make-state-seq [state]
(or (:order (@meta-state state))
(swap! state-seq inc)))
(deftype NotStartedState [state]
Object
(toString [this]
(deftype NotStartedState [state]
Object
(toString [this]
(str "'" state "' is not started (to start all the states call mount/start)")))
;;TODO validate the whole lifecycle
(defn- validate [{:keys [start stop suspend resume] :as lifecycle}]
(cond
(cond
(not start) (throw-runtime "can't start a stateful thing without a start function. (i.e. missing :start fn)")
(and suspend
(not resume)) (throw-runtime "suspendable state should have a resume function (i.e. missing :resume fn)")))
(or suspend resume) (throw-runtime "suspend / resume lifecycle support was removed in \"0.1.10\" in favor of (mount/stop-except)")))
(defn- with-ns [ns name]
(str "#'" ns "/" name))
@ -42,28 +45,18 @@
(nth f 2) ;; magic 2 is to get the body => ["fn*" "[]" "(fn body)"]
f))
(defn- cleanup-if-dirty
(defn cleanup-if-dirty
"in case a namespace is recompiled without calling (mount/stop),
a running state instance will still be running.
this function stops this 'lost' state instance.
it is meant to be called by defstate before defining a new state"
[state]
(when-let [stop (@running state)]
(prn (str "<< stopping.. " state " (namespace was recompiled)"))
(stop)
[state reason]
(when-let [{:keys [stop] :as up} (@running state)]
(when stop
(log (str "<< stopping.. " state " " reason))
(stop))
(swap! running dissoc state)))
#?(:clj
(defn current-state [state]
(let [{:keys [inst var]} (@meta-state state)]
(if (= @mode :cljc)
@inst
(var-get var))))
:cljs
(defn current-state [state]
(-> (@meta-state state) :inst deref)))
#?(:clj
(defn alter-state! [{:keys [var inst]} value]
(if (= @mode :cljc)
@ -82,39 +75,36 @@
(swap! done conj state-name)
state))
(defn- up [state {:keys [start stop resume status] :as current} done]
(defn- up [state {:keys [start stop status] :as current} done]
(when-not (:started status)
(let [s (on-error (str "could not start [" state "] due to")
(if (:suspended status)
(record! state resume done)
(record! state start done)))]
(let [s (on-error (str "could not start [" state "] due to")
(record! state start done))]
(alter-state! current s)
(swap! running assoc state stop)
(swap! running assoc state {:stop stop})
(update-meta! [state :status] #{:started}))))
(defn- down [state {:keys [stop status] :as current} done]
(when (some status #{:started :suspended})
(when stop
(on-error (str "could not stop [" state "] due to")
(record! state stop done)))
(alter-state! current (NotStartedState. state)) ;; (!) if a state does not have :stop when _should_ this might leak
(defn- down
"brings a state down by
* calling its 'stop' function if it is defined
* if not defined, state will still become a 'NotStartedState'
* in case of a failure on 'stop', state is still marked as :stopped, and the error is logged / printed
* dissoc'ing it from the running states
* marking it as :stopped"
[state {:keys [stop status] :as current} done]
(when (some status #{:started})
(if stop
(if-let [cause (-> (on-error (str "could not stop [" state "] due to")
(record! state stop done)
:fail? false)
:f-failed)]
(log cause :error) ;; this would mostly be useful in REPL / browser console
(alter-state! current (->NotStartedState state)))
(alter-state! current (->NotStartedState state))) ;; (!) if a state does not have :stop when _should_ this might leak
(swap! running dissoc state)
(update-meta! [state :status] #{:stopped})))
(defn- sigstop [state {:keys [resume suspend status] :as current} done]
(when (and (:started status) resume) ;; can't have suspend without resume, but the reverse is possible
(when suspend ;; don't suspend if there is only resume function (just mark it :suspended?)
(let [s (on-error (str "could not suspend [" state "] due to")
(record! state suspend done))]
(alter-state! current s)))
(update-meta! [state :status] #{:suspended})))
(defn- sigcont [state {:keys [resume status] :as current} done]
(when (:suspended status)
(let [s (on-error (str "could not resume [" state "] due to")
(record! state resume done))]
(alter-state! current s)
(update-meta! [state :status] #{:started}))))
(defn running-states []
(set (keys @running)))
(deftype DerefableState [name]
#?(:clj clojure.lang.IDeref
@ -122,40 +112,91 @@
(#?(:clj deref
:cljs -deref)
[_]
(let [{:keys [status inst] :as state} (@meta-state name)]
(let [{:keys [status var inst] :as state} (@meta-state name)]
(when-not (:started status)
(up name state (atom #{})))
@inst)))
(if (= :throw (-> var meta :on-lazy-start))
(throw-runtime (str ":on-lazy-start is set to :throw i.e. (defstate {:on-lazy-start :throw} " name "...) "
"and " name " state was not explicitly started before it was deref'ed (i.e. @" name ")"))
(up name state (atom #{}))))
@inst))
#?(:clj clojure.lang.IPending
:cljs IPending)
(#?(:clj isRealized
:cljs -realized?)
[_]
(boolean ((running-states) name))))
#?(:clj
(defmacro defstate [state & body]
(let [[state params] (macro/name-with-attributes state body)
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)
state-name (with-ns *ns* state)
order (make-state-seq state-name)
sym (str state)]
(validate lifecycle)
(cleanup-if-dirty state-name)
(let [s-meta (cond-> {:order order
:start `(fn [] ~start)
:status #{:stopped}}
stop (assoc :stop `(fn [] ~stop))
suspend (assoc :suspend `(fn [] ~suspend))
resume (assoc :resume `(fn [] ~resume)))]
`(do
(def ~state (DerefableState. ~state-name))
((var mount.core/update-meta!) [~state-name] (assoc ~s-meta :inst (atom (NotStartedState. ~state-name))
:var (var ~state)))
(var ~state))))))
(defn current-state [state]
(let [{:keys [var]} (@meta-state state)]
(if (= @mode :cljc)
(->DerefableState state)
(var-get var))))
#?(:clj
(defmacro defstate! [state & {:keys [start! stop!]}]
(let [state-name (with-ns *ns* state)]
`(defstate ~state
:start (~'let [~state (mount/current-state ~state-name)]
~start!)
:stop (~'let [~state (mount/current-state ~state-name)]
~stop!)))))
:cljs
(defn current-state [state]
(-> (@meta-state state) :inst deref)))
(defn on-reload-meta [s-var]
(or (-> s-var meta :on-reload)
:restart)) ;; restart by default on ns reload
(defn running-noop? [s-name]
(let [{:keys [var status]} (@meta-state s-name)
on-reload (-> var meta :on-reload)]
(when status
(and (status :started)
(= :noop on-reload)))))
;;TODO: make private after figuring out the inconsistency betwen cljs compile stages
;; (i.e. _sometimes_ this, if private, is not seen by expanded "defmacro" on cljs side)
(defn mount-it [s-var s-name s-meta]
(let [with-inst (assoc s-meta :inst (atom (->NotStartedState s-name))
:var s-var)
on-reload (on-reload-meta s-var)
existing? (when-not (= :noop on-reload)
(cleanup-if-dirty s-name "(namespace was recompiled)"))]
(update-meta! [s-name] with-inst)
(when (and existing? (= :restart on-reload))
(log (str ">> starting.. " s-name " (namespace was recompiled)"))
(up s-name with-inst (atom #{})))))
(deftime
(defmacro defstate
"defines a state (a.k.a. a stateful component).
restarts on recompilation.
pass ^{:on-reload :noop} to prevent auto-restart on ns recompilation,
or ^{:on-reload :stop} to stop on recompilation."
[state & body]
(let [[state params] (mount.tools.macro/name-with-attributes state body)
{:keys [start stop] :as lifecycle} (apply hash-map params)
state-name (with-ns *ns* state)
order (make-state-seq state-name)]
(validate lifecycle)
(let [s-meta (cond-> {:order order
:start `(fn [] ~start)
:status #{:stopped}}
stop (assoc :stop `(fn [] ~stop)))]
`(do
;; (log (str "|| mounting... " ~state-name))
;; only create/redefine a new state iff this is not a running ^{:on-reload :noop}
(if-not (running-noop? ~state-name)
(do
(~'defonce ~state (->DerefableState ~state-name))
(mount-it (~'var ~state) ~state-name ~s-meta))
(~'defonce ~state (current-state ~state-name)))
(~'var ~state)))))
(defmacro defstate! [state & {:keys [start! stop!]}]
(let [state-name (with-ns *ns* state)]
`(defstate ~state
:start (~'let [~state (mount.core/current-state ~state-name)]
~start!)
:stop (~'let [~state (mount.core/current-state ~state-name)]
~stop!))))
)
(defn in-cljc-mode []
(reset! mode :cljc))
@ -166,25 +207,9 @@
;;TODO args might need more thinking
(defn args [] @-args)
(defn- find-all-states []
(defn find-all-states []
(keys @meta-state))
;;TODO ns based for now. need to be _state_ based
#_(defn- add-deps [{:keys [ns] :as state} all]
(let [refers (ns-refers ns)
any (set all)
deps (filter (comp any val) refers)]
(assoc state :deps deps)))
#_(defn states-with-deps []
(let [all (find-all-states)]
(->> (map (comp #(add-deps % all)
#(select-keys % [:name :order :ns :status])
meta)
all)
(sort-by :order))))
#?(:clj
(defn- var-to-str [v]
(str v)))
@ -196,36 +221,54 @@
(with-ns ns name))
v)))
(defn- unvar-state [s]
(->> s (drop 2) (apply str))) ;; magic 2 is removing "#'" in state name
#?(:clj
(defn- was-removed?
"checks if a state was removed from a namespace"
[state]
(-> state unvar-state symbol resolve not)))
#?(:clj
(defn cleanup-deleted [state]
(when (was-removed? state)
(cleanup-if-dirty state "(it was deleted)")
(swap! meta-state dissoc state))))
(defn- bring [states fun order]
(let [done (atom [])]
(as-> states $
(as-> states $
(map var-to-str $)
#?(:clj ;; needs more thking in cljs, since based on sym resolve
(remove cleanup-deleted $))
(select-keys @meta-state $)
(sort-by (comp :order val) order $)
(doseq [[k v] $] (fun k v done)))
@done))
(defn- merge-lifecycles
"merges with overriding _certain_ non existing keys.
i.e. :suspend is in a 'state', but not in a 'substitute': it should be overriden with nil
"merges with overriding _certain_ non existing keys.
i.e. :stop is in a 'state', but not in a 'substitute': it should be overriden with nil
however other keys of 'state' (such as :ns,:name,:order) should not be overriden"
([state sub]
(merge-lifecycles state nil sub))
([state origin {:keys [start stop suspend resume status]}]
(assoc state :origin origin
([state origin {:keys [start stop status]}]
(assoc state :origin origin
:status status
:start start :stop stop :suspend suspend :resume resume)))
:start start :stop stop)))
(defn- rollback! [state]
(let [{:keys [origin] :as sub} (@meta-state state)]
(when origin
(update-meta! [state] (merge-lifecycles sub origin)))))
(defn- substitute! [state with]
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
(defn- substitute! [state with mode]
(let [lifecycle-fns #(select-keys % [:start :stop :status])
origin (@meta-state state)
sub (@meta-state with)]
(update-meta! [with :sub?] true)
sub (if (= :value mode)
{:start (fn [] with) :status :stopped}
(assoc with :status :stopped))]
(update-meta! [state] (merge-lifecycles origin (lifecycle-fns origin) sub))))
(defn- unsub [state]
@ -236,32 +279,125 @@
(remove (comp :sub? @meta-state) (find-all-states)))
(defn start [& states]
(let [states (or (seq states) (all-without-subs))]
{:started (bring states up <)}))
(let [fs (-> states first)]
(if (coll? fs)
(if-not (empty? fs) ;; (mount/start) vs. (mount/start #{}) vs. (mount/start #{1 2 3})
(apply start fs)
{:started #{}})
(let [states (or (->> states (map var-to-str) seq)
(all-without-subs))]
{:started (bring states up <)}))))
(defn stop [& states]
(let [states (or states (find-all-states))
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with"
stopped (bring states down >)]
(dorun (map rollback! states)) ;; restore to origin from "start-with"
{:stopped stopped}))
(let [fs (-> states first)]
(if (coll? fs)
(if-not (empty? fs) ;; (mount/stop) vs. (mount/stop #{}) vs. (mount/stop #{1 2 3})
(apply stop fs)
{:stopped #{}})
(let [states (or (->> states (map var-to-str) seq)
(find-all-states))
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with" / "swap-states"
stopped (bring states down >)]
(dorun (map rollback! states)) ;; restore to origin from "start-with" / "swap-states"
{:stopped stopped}))))
;; composable set of states
(defn- mapset [f xs]
(-> (map f xs)
set))
(defn only
([these]
(only (find-all-states) these))
([states these]
(intersection (mapset var-to-str these)
(mapset var-to-str states))))
(defn with-args
([args]
(with-args (find-all-states) args))
([states args]
(reset! -args args) ;; TODO localize
states))
(defn except
([these]
(except (find-all-states) these))
([states these]
(remove (mapset var-to-str these)
(mapset var-to-str states))))
(defn swap
([with]
(swap (find-all-states) with))
([states with]
(doseq [[from to] with]
(substitute! (var-to-str from)
to :value))
states))
(defn swap-states
([with]
(swap-states (find-all-states) with))
([states with]
(doseq [[from to] with]
(substitute! (var-to-str from)
to :state))
states))
;; restart on events
(defprotocol ChangeListener
(add-watcher [this ks watcher])
(on-change [this k]))
(deftype RestartListener [watchers]
ChangeListener
(add-watcher [_ ks state]
(doseq [k ks]
(swap! watchers update k (fn [v]
(-> (conj v state) vec)))))
(on-change [_ ks]
(doseq [k ks]
(when-let [states (seq (@watchers k))]
(apply stop states)
(apply start states)))))
(defn restart-listener
([]
(restart-listener {}))
([watchers]
(RestartListener. (atom watchers))))
;; explicit, not composable (subject to depreciate?)
(defn stop-except [& states]
(let [all (set (find-all-states))
states (map var-to-str states)
states (remove (set states) all)]
(apply stop states)))
(if-not (empty? states)
(apply stop states)
{:stopped #{}})))
(defn start-with-args [xs & states]
(reset! -args xs)
(if (first states)
(start states)
(apply start states)
(start)))
(defn start-with [with]
(doseq [[from to] with]
(substitute! (var-to-str from)
(var-to-str to)))
to :value))
(start))
(defn start-with-states [with]
(doseq [[from to] with]
(substitute! (var-to-str from)
to :state))
(start))
(defn start-without [& states]
@ -269,13 +405,7 @@
(let [app (set (all-without-subs))
states (map var-to-str states)
without (remove (set states) app)]
(apply start without))
(if-not (empty? without)
(apply start without)
{:started #{}}))
(start)))
(defn suspend [& states]
(let [states (or (seq states) (all-without-subs))]
{:suspended (bring states sigstop <)}))
(defn resume [& states]
(let [states (or (seq states) (all-without-subs))]
{:resumed (bring states sigcont <)}))

View file

@ -0,0 +1,26 @@
(ns mount.tools.graph)
#?(:clj
;;TODO ns based for now. need to be _state_ based. or better yet need to have a real graph :)
(defn- add-deps [{:keys [ns] :as state} states]
(let [refers (ns-refers ns)
any (->> states vals (map :var) set)
deps (->> (filter (comp any val) refers)
(map (comp str second))
set)]
(assoc (dissoc state :ns)
:deps deps))))
#?(:clj
(defn- meta-with-ns [[sname {:keys [var] :as smeta}]]
(let [sns (-> var meta :ns)]
(assoc smeta :ns sns :name sname))))
#?(:clj
(defn states-with-deps []
(let [states @@#'mount.core/meta-state]
(->> (map (comp #(add-deps % states)
#(select-keys % [:name :order :ns :status])
meta-with-ns)
states)
(sort-by :order)))))

View file

@ -0,0 +1,20 @@
(ns mount.tools.logger
#?@(:cljs [(:require [goog.log :as glog])
(:import [goog.debug Console])]))
#?(:cljs
(defonce ^:dynamic *logger*
(do
(.setCapturing (Console.) true)
(glog/getLogger "mount" nil))))
#?(:clj
(defn log [msg & _]
(prn msg)))
#?(:cljs
(defn log [msg & level]
(case (first level)
:error (glog/error *logger* msg nil)
(glog/info *logger* msg nil))))

View file

@ -1,19 +1,28 @@
(ns mount.tools.macro
#?(:cljs (:require-macros [mount.tools.macro])))
(:refer-clojure :exclude [case])
#?(:cljs (:require-macros [mount.tools.macrovich :refer [deftime]])
:clj (:require [mount.tools.macrovich :refer [deftime]])))
#?(:clj
(defmacro on-error [msg f]
`(try
~f
(catch #?(:clj Throwable
:cljs :default) t#
(throw #?(:clj (RuntimeException. ~msg t#)
:cljs (js/Error (str ~msg (.-stack t#)))))))))
(deftime
#?(:clj
(defmacro throw-runtime [msg]
`(throw #?(:clj (RuntimeException. ~msg)
:cljs (js/Error (str ~msg))))))
(defmacro on-error [msg f & {:keys [fail?]
:or {fail? true}}]
`(mount.tools.macrovich/case
:clj (try ~f
(catch Throwable t#
(if ~fail?
(throw (RuntimeException. ~msg t#))
{:f-failed (ex-info ~msg {} t#)})))
:cljs (try ~f
(catch :default t#
(if ~fail?
(throw (js/Error (~'str ~msg " " t#)))
{:f-failed (ex-info ~msg {} t#)})))))
(defmacro throw-runtime [msg]
`(throw (mount.tools.macrovich/case :clj (RuntimeException. ~msg) :cljs (~'str ~msg))))
)
;; this is a one to one copy from https://github.com/clojure/tools.macro
;; to avoid a lib dependency for a single function

View file

@ -0,0 +1,21 @@
(ns ^{:doc "From https://github.com/cgrand/macrovich. Licensed under EPL v1.
Copyright Cristophe Grand." }
mount.tools.macrovich
(:refer-clojure :exclude [case]))
(defmacro deftime
"This block will only be evaluated at the correct time for macro definition, at other times its content
are removed.
For Clojure it always behaves like a `do` block.
For Clojurescript/JVM the block is only visible to Clojure.
For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace."
[& body]
(when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*))))
`(do ~@body)))
(defmacro case [& {:keys [cljs clj]}]
(if (contains? &env '&env)
`(if (:ns ~'&env) ~cljs ~clj)
(if #?(:clj (:ns &env) :cljs true)
cljs
clj)))

View file

@ -7,9 +7,9 @@
(defn load-config [path]
(info "loading config from" path)
(-> path
slurp
(-> path
slurp
edn/read-string))
(defstate config
(defstate config
:start (load-config "dev/resources/config.edn"))

View file

@ -30,7 +30,7 @@
:db/valueType :db.type/bigdec
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id [:db.part/db]
:db/ident :order/qty
:db/valueType :db.type/long
@ -51,11 +51,11 @@
:order/bid bid
:order/offer offer
:order/qty qty}]))
(defn find-orders [ticker] ;; can take connection as param
(let [orders (d/q '[:find ?e :in $ ?ticker
:where [?e :order/symbol ?ticker]]
:where [?e :order/symbol ?ticker]]
(d/db @nyse/conn) ticker)]
(touch @nyse/conn orders)))

View file

@ -6,8 +6,9 @@
(defn entity [conn id]
(d/entity (d/db conn) id))
(defn touch [conn results]
(defn touch
"takes 'entity ids' results from a query
e.g. '#{[272678883689461] [272678883689462] [272678883689459] [272678883689457]}'"
[conn results]
(let [e (partial entity conn)]
(map #(-> % first e d/touch) results)))

View file

@ -1,4 +1,4 @@
(ns tapp.utils.logging ;; << change to your namespace/path
(ns tapp.utils.logging ;; << change to your namespace/path
(:require [mount.core]
[robert.hooke :refer [add-hook clear-hooks]]
[clojure.string :refer [split]]
@ -6,41 +6,38 @@
(alter-meta! *ns* assoc ::load false)
(defn- f-to-action [f]
(defn- f-to-action [f {:keys [status]}]
(let [fname (-> (str f)
(split #"@")
first)]
(case fname
"mount.core$up" :up
"mount.core$down" :down
"mount.core$sigstop" :suspend
"mount.core$sigcont" :resume
"mount.core$up" (when-not (:started status) :up)
"mount.core$down" (when-not (:stopped status) :down)
:noop)))
(defn whatcha-doing? [{:keys [status suspend]} action]
(defn whatcha-doing? [action]
(case action
:up (if (status :suspended) ">> resuming"
(if-not (status :started) ">> starting"))
:down (if (or (status :started) (status :suspended)) "<< stopping")
:suspend (if (and (status :started) suspend) "<< suspending")
:resume (if (status :suspended) ">> resuming")))
:up ">> starting"
:down "<< stopping"
false))
(defn log-status [f & args]
(let [{:keys [var] :as state} (second args)
action (f-to-action f)]
(when-let [taking-over-the-world (whatcha-doing? state action)]
(info (str taking-over-the-world ".. " var)))
(defn log-status [f & args]
(let [[state-name state] args
action (f-to-action f state)]
(when-let [taking-over-the-world (whatcha-doing? action)]
(info (str taking-over-the-world ".. " state-name)))
(apply f args)))
(defonce lifecycle-fns
#{#'mount.core/up
#'mount.core/down
#'mount.core/sigstop
#'mount.core/sigcont})
#'mount.core/down})
(defn without-logging-status []
(doall (map #(clear-hooks %) lifecycle-fns)))
;; this is the one to use:
(defn with-logging-status []
(without-logging-status)
(doall (map #(add-hook % log-status) lifecycle-fns)))

View file

@ -13,13 +13,13 @@
(defn find-source-logs [db source]
(d/q '{:find [?t ?msg]
:in [$ ?s]
:in [$ ?s]
:where [[?e :source ?s]
[?e :timestamp ?t]
[?e :msg ?msg]]}
@@db source))
(defn find-all-logs [db]
(->> (map :e (d/datoms @@db :aevt :timestamp))
(->> (map :e (d/datoms @@db :aevt :timestamp))
dedupe
(d/pull-many @@db '[:timestamp :source :msg])))

View file

@ -12,7 +12,7 @@
" &#8594; [" (name source) "]: " msg))
(defn show-log []
(.write js/document
(.write js/document
(html [:ul (doall (for [e (find-all-logs log)]
[:li (format-log-event e)]))])))

View file

@ -12,7 +12,8 @@
mount.test.stop-except
mount.test.start-without
mount.test.start-with
mount.test.suspend-resume
mount.test.start-with-states
mount.test.printing
))
#?(:clj (alter-meta! *ns* assoc ::load false))
@ -30,7 +31,8 @@
'mount.test.stop-except
'mount.test.start-without
'mount.test.start-with
'mount.test.suspend-resume
'mount.test.start-with-states
'mount.test.printing
))
(defn run-tests []

View file

@ -0,0 +1,40 @@
(ns mount.test.cleanup-deleted-states
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]
[tapp.example]])
[mount.test.helper :refer [dval helper forty-two]]))
(def status (atom :a-not-started))
(defstate a :start (reset! status :a-started)
:stop (reset! status :a-stopped))
#?(:clj (alter-meta! *ns* assoc ::load false))
#?(:clj
(deftest cleanup-deleted-state
(testing "should start all and remove/delete state from ns"
(let [started (-> (mount/start) :started set)]
(is (some #{"#'mount.test.cleanup-deleted-states/a"}
started))
(is (= :a-started @status))
(ns-unmap 'mount.test.cleanup-deleted-states 'a)
(is (nil? (resolve 'mount.test.cleanup-deleted-states/a)))))
(testing "should cleanup/stop a state after it was deleted from ns"
(is (empty? (:started (mount/start)))) ;; on any mount op (not necessarily on "stop")
(is (= :a-stopped @status))
(is (not (some #{"#'mount.test.cleanup-deleted-states/a"}
(keys @@#'mount.core/meta-state)))))
(testing "should not stop it again on stop (should not be there by this point)")
(is (not (some #{"#'mount.test.cleanup-deleted-states/a"}
(-> (mount/stop) :stopped set))))))
;; (t/run-tests)

View file

@ -22,6 +22,24 @@
(mount/stop)
(is (instance? mount.core.NotStartedState (dval tapp.example/nrepl))))))
#?(:clj
(deftest restart-on-recompile
(let [_ (mount/start)
before (:server-socket (dval tapp.example/nrepl))]
(require 'tapp.example :reload)
(is (not= before (:server-socket (dval tapp.example/nrepl)))) ;; should have restarted on recompile/reload, hence different reference
(mount/stop)
(is (instance? mount.core.NotStartedState (dval tapp.example/nrepl))))))
#?(:clj
(deftest start-on-recompile
(let [_ (mount/start)
before (dval tapp.conf/config)]
(require 'tapp.conf :reload)
(is (not (identical? before (dval tapp.conf/config)))) ;; should be a newly recompiled map
(mount/stop)
(is (instance? mount.core.NotStartedState (dval tapp.conf/config))))))
#?(:cljs
(deftest cleanup-dirty-states
(let [_ (mount/start #'mount.test.helper/helper)]
@ -30,7 +48,7 @@
(.require js/goog "mount.test.helper") ;; should have run :stop of `helper`
;; (is (= :cleaned @forty-two)) ;; TODO: figure out how to reload a namespace properly
;; (is (instance? mount.core.NotStartedState (dval helper)))
(mount/start #'mount.test.helper/helper)
(mount/start #'mount.test.helper/helper)
(is (= :started (dval helper)))
(mount/stop)
(is (instance? mount.core.NotStartedState (dval helper))))))

View file

@ -0,0 +1,190 @@
(ns mount.test.composable-fns
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[clojure.set :refer [intersection]]
[mount.core :refer [only except swap swap-states with-args] :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[clojure.set :refer [intersection]]
[clojure.tools.nrepl.server :refer [start-server stop-server]]
[mount.core :as mount :refer [defstate only except swap swap-states with-args]]
[tapp.conf :refer [config]]
[tapp.nyse :refer [conn]]
[tapp.example :refer [nrepl]]])
[mount.test.helper :refer [dval helper]]))
#?(:clj (alter-meta! *ns* assoc ::load false))
(defstate test-conn :start 42
:stop (constantly 0))
(defstate test-nrepl :start [])
(def swap-conn {:start (fn [] 42)
:stop #(println "stopping test-conn-state")})
#?(:clj
(def swap-nrepl {:start #(start-server :bind "localhost" :port 3442)
:stop #(stop-server @nrepl)}))
#?(:clj
(deftest only-states
(testing "only should only return given states.
if source set of states is not provided, it should use all the states to select from"
(is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl" "#'tapp.nyse/conn"}
(only #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}))))
(testing "only should only return given states"
(is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl"}
(only [#'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn]
#{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl}))))))
#?(:clj
(deftest except-states
(testing "except should exclude given states.
if source set of states is not provided, it should use all the states to exclude from"
(let [states (except #{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn})]
(is (coll? states))
(is (pos? (count states)))
(is (zero? (count (intersection (set states)
#{"#'tapp.example/nrepl" "#'tapp.nyse/conn" "#'is.not/here"}))))))
(testing "except should exclude given states"
(is (= #{"#'tapp.conf/config" "#'mount.test.composable-fns/test-conn"}
(set (except #{#'tapp.example/nrepl #'tapp.conf/config #'mount.test.composable-fns/test-conn}
#{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn})))))))
#?(:clj
(deftest states-with-args
(testing "with-args should set args and return all states if none provided"
(let [states (with-args {:a 42})]
(is (= {:a 42} (mount/args)))
(is (= states (#'mount.core/find-all-states)))))
(testing "with-args should set args and thread states if provided"
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}
states (with-args t-states {:a 42})]
(is (= {:a 42} (mount/args)))
(is (= states t-states))))))
#?(:clj
(deftest swap-states-with-values
(testing "swap should swap states with values and return all states if none is given"
(let [states (swap {#'tapp.nyse/conn "conn-sub"
#'tapp.example/nrepl :nrepl-sub})]
(is (= states (#'mount.core/find-all-states)))
(mount/start)
(is (map? (dval config)))
(is (= :nrepl-sub (dval nrepl)))
(is (= "conn-sub" (dval conn)))
(mount/stop)))
(testing "swap should swap states with values and return only states that it is given"
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}
states (swap t-states {#'tapp.nyse/conn "conn-sub"
#'tapp.example/nrepl :nrepl-sub})]
(is (= states t-states))
(mount/start)
(is (map? (dval config)))
(is (= :nrepl-sub (dval nrepl)))
(is (= "conn-sub" (dval conn)))
(is (= 42 (dval test-conn)))
(mount/stop)))))
#?(:clj
(deftest swap-states-with-states
(testing "swap-states should swap states with states and return all mount states if none is given"
(let [states (swap-states {#'tapp.nyse/conn swap-conn
#'tapp.example/nrepl swap-nrepl})]
(is (= states (#'mount.core/find-all-states)))
(mount/start)
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (= 42 (dval conn)))
(mount/stop)))
(testing "swap-states should swap states on start and rollback on stop"
(let [states (swap-states {#'tapp.nyse/conn swap-conn})]
(is (= states (#'mount.core/find-all-states)))
(mount/start #'tapp.nyse/conn)
(is (= 42 (dval conn)))
(mount/stop #'tapp.nyse/conn)
(mount/start #'tapp.nyse/conn)
(is (instance? datomic.peer.LocalConnection (dval conn)))
(mount/stop)))
(testing "swap-states should swap states with states and return only states that it is given"
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.nyse/conn}
states (swap-states t-states {#'tapp.nyse/conn swap-conn
#'tapp.example/nrepl swap-nrepl})]
(is (= states t-states))
(apply mount/start states)
(is (instance? mount.core.NotStartedState (dval config)))
(is (instance? mount.core.NotStartedState (dval nrepl)))
(is (= 42 (dval conn)))
(is (= 42 (dval test-conn))) ;; test-conn is explicitly started via "t-states"
(mount/stop)))))
#?(:clj
(deftest composing
(testing "states provided to the top level should narrow down the scope for the whole composition"
(let [scope [#'tapp.conf/config
#'tapp.example/nrepl
#'tapp.nyse/conn
#'mount.test.composable-fns/test-nrepl
#'mount.test.composable-fns/test-conn]
states (-> (only scope)
(with-args {:a 42})
(except [#'mount.test.composable-fns/test-nrepl
#'mount.test.composable-fns/test-conn])
(swap-states {#'tapp.example/nrepl swap-nrepl})
(swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}}))]
(is (= #{"#'tapp.nyse/conn" "#'tapp.conf/config" "#'tapp.example/nrepl"} (set states)))
(mount/start states)
(is (= {:a 42} (mount/args)))
(is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(mount/stop)))
(testing "should compose and start in a single composition"
(let [scope [#'tapp.conf/config
#'tapp.example/nrepl
#'tapp.nyse/conn
#'mount.test.composable-fns/test-nrepl
#'mount.test.composable-fns/test-conn]]
(-> (only scope)
(with-args {:a 42})
(except [#'mount.test.composable-fns/test-nrepl
#'mount.test.composable-fns/test-conn])
(swap-states {#'tapp.example/nrepl swap-nrepl})
(swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}})
mount/start)
(is (= {:a 42} (mount/args)))
(is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(mount/stop)))
(testing "should not start anything on empty seq of states"
(let [scope #{}]
(is (= {:started #{}} (-> (only scope)
mount/start)))
(mount/stop)))
(testing "should not stop anything on empty seq of states"
(let [scope #{}]
(mount/start)
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= {:stopped #{}} (-> (only scope)
mount/stop)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(mount/stop)
(is (instance? mount.core.NotStartedState (dval conn)))))))

View file

@ -43,7 +43,7 @@
#'mount.test.fun-with-values/private-f
#'mount.test.fun-with-values/f-value))
(use-fixtures :once
(use-fixtures :once
#?(:cljs {:before start-states
:after mount/stop}
:clj #((start-states) (%) (mount/stop))))

View file

@ -5,10 +5,10 @@
#?(:clj (alter-meta! *ns* assoc ::load false))
(defn dval
(defn dval
"returns a value of DerefableState without deref'ing it"
[d]
(-> (@@(var mount.core/meta-state)
(-> (@@(var mount.core/meta-state)
#?(:clj (.name d)
:cljs (.-name d)))
:inst
@ -18,3 +18,11 @@
(defstate helper :start :started
:stop (reset! forty-two :cleaned))
(def counter (atom {:a {:started 0 :stopped 0}
:b {:started 0 :stopped 0}
:c {:started 0 :stopped 0}}))
(defn inc-counter [state status]
(swap! counter update-in [state status] inc)
status)

View file

@ -0,0 +1,55 @@
(ns mount.test.on-reload
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]
[tapp.example]])
[mount.test.helper :refer [dval helper forty-two counter inc-counter]]
[mount.test.on-reload-helper :refer [a b c]]))
#?(:clj (alter-meta! *ns* assoc ::load false))
#?(:clj
(defn abc [f]
(mount/start #'mount.test.on-reload-helper/a
#'mount.test.on-reload-helper/b
#'mount.test.on-reload-helper/c)
(f)
(mount/stop)))
(use-fixtures :each
#?(:cljs {:before #(mount/start #'mount.test.on-reload-helper/a
#'mount.test.on-reload-helper/b
#'mount.test.on-reload-helper/c)
:after mount/stop}
:clj abc))
#?(:clj
(deftest restart-by-default
(is (= '(:started) (distinct (map dval [a b c]))))
(let [pre-reload @counter]
(require 'mount.test.on-reload-helper :reload)
;; "a" is marked as :noop on reload
;; previous behavior left a stale reference =>>> ;; (is (instance? mount.core.NotStartedState (dval a))) ;; (!) stale reference of old a is still there somewhere
(is (= :started (dval a))) ;; make sure a still has the same instance as before reload
(is (= (-> pre-reload :a) ;; and the start was not called: the counter did not change
(-> @counter :a)))
;; "b" is marked as :stop on reload
(is (instance? mount.core.NotStartedState (dval b)))
(is (= (-> pre-reload :b :started)
(-> @counter :b :started)))
(is (= (inc (-> pre-reload :b :stopped))
(-> @counter :b :stopped)))
;; "c" is not marked on reload, using "restart" as default
(is (= :started (dval c)))
(is (= (inc (-> pre-reload :c :started))
(-> @counter :c :started)))
(is (= (inc (-> pre-reload :c :stopped))
(-> @counter :c :stopped))))))

View file

@ -0,0 +1,21 @@
(ns mount.test.on-reload-helper
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]
[tapp.example]])
[mount.test.helper :refer [inc-counter]]))
(defstate ^{:on-reload :noop} a :start (inc-counter :a :started)
:stop (inc-counter :a :stopped))
(defstate ^{:on-reload :stop} b :start (inc-counter :b :started)
:stop (inc-counter :b :stopped))
(defstate c :start (inc-counter :c :started)
:stop (inc-counter :c :stopped))

View file

@ -20,18 +20,18 @@
(f)
(mount/stop)))
(use-fixtures :once
(use-fixtures :once
#?(:cljs {:before #(mount/start #'tapp.conf/config #'tapp.audit-log/log)
:after mount/stop}
:clj with-parts))
#?(:clj
(deftest start-only-parts
(deftest start-only-parts
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (instance? mount.core.NotStartedState (dval should-not-start)))))
#?(:cljs
(deftest start-only-parts
(deftest start-only-parts
(is (instance? datascript.db/DB @(dval log)))
(is (map? (dval config)))
(is (instance? mount.core.NotStartedState (dval should-not-start)))

View file

@ -0,0 +1,17 @@
(ns mount.test.printing
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]])))
#?(:clj (alter-meta! *ns* assoc ::load false))
(defstate foo
:start (do (println "Starting!") 42))
(deftest test-printing-has-no-side-effects
;; Test that printing an unstarted DerefableState does not have the
;; side-effect of starting it
(println foo)
(is (not= 42 foo)))

View file

@ -9,11 +9,11 @@
#?(:clj (alter-meta! *ns* assoc ::load false))
(use-fixtures :once
(use-fixtures :once
#?(:cljs {:before #(mount/start #'mount.test.fun-with-values/private-f)
:after mount/stop}
:clj #((mount/start #'mount.test.fun-with-values/private-f)
(%)
:clj #((mount/start #'mount.test.fun-with-values/private-f)
(%)
(mount/stop))))
(deftest fun-with-values

View file

@ -23,20 +23,14 @@
(deftest start-with
(testing "should start with substitutes"
(let [_ (mount/start-with {#'tapp.websockets/system-a #'mount.test.start-with/test-conn
#'mount.test.helper/helper #'mount.test.start-with/test-nrepl})]
(let [_ (mount/start-with {#'tapp.websockets/system-a "system-a-sub"
#'mount.test.helper/helper "helper-sub"})]
(is (map? (dval config)))
(is (vector? (dval helper)))
(is (= (dval system-a) 42))
(is (= "helper-sub" (dval helper)))
(is (= "system-a-sub" (dval system-a)))
(is (instance? datascript.db/DB @(dval log)))
(mount/stop)))
(testing "should not start the substitute itself"
(let [_ (mount/start-with {#'tapp.websockets/system-a #'mount.test.start-with/test-conn})]
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (= 42 (dval system-a)))
(mount/stop)))
(testing "should start normally after start-with"
(let [_ (mount/start)]
(is (map? (dval config)))
@ -57,22 +51,16 @@
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
(mount/stop)))))
#?(:clj
(deftest start-with
(testing "should start with substitutes"
(let [_ (mount/start-with {#'tapp.nyse/conn #'mount.test.start-with/test-conn
#'tapp.example/nrepl #'mount.test.start-with/test-nrepl})]
(let [_ (mount/start-with {#'tapp.nyse/conn "conn-sub"
#'tapp.example/nrepl :nrepl-sub})]
(is (map? (dval config)))
(is (vector? (dval nrepl)))
(is (= (dval conn) 42))
(mount/stop)))
(testing "should not start the substitute itself"
(let [_ (mount/start-with {#'tapp.nyse/conn #'mount.test.start-with/test-conn})]
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (= (dval conn) 42))
(is (= :nrepl-sub (dval nrepl)))
(is (= "conn-sub" (dval conn)))
(mount/stop)))
(testing "should start normally after start-with"

View file

@ -0,0 +1,105 @@
(ns mount.test.start-with-states
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]
[clojure.tools.nrepl.server :refer [start-server stop-server]]
[tapp.conf :refer [config]]
[tapp.nyse :refer [conn]]
[tapp.example :refer [nrepl]]])
[mount.test.helper :refer [dval helper]]))
#?(:clj (alter-meta! *ns* assoc ::load false))
(defstate test-conn :start 42
:stop (constantly 0))
(defstate test-nrepl :start [])
(def swap-conn {:start (fn [] 42)
:stop #(println "stopping test-conn-state")})
#?(:clj
(def swap-nrepl {:start #(start-server :bind "localhost" :port 3442)
:stop #(stop-server @nrepl)})
:cljs
(def swap-nrepl {:start (fn [] :nrepl)
:stop (fn [] :stopped-nrepl)}))
#?(:cljs
(deftest start-with-states
(testing "should start with substitutes"
(let [_ (mount/start-with-states {#'tapp.websockets/system-a swap-conn
#'mount.test.helper/helper swap-nrepl})]
(is (map? (dval config)))
(is (= (:nrepl (dval helper))))
(is (= (dval system-a) 42))
(is (instance? datascript.db/DB @(dval log)))
(mount/stop)))
#_(testing "should not start the substitute itself" ;; was true when subbing with exsiting states
(let [_ (mount/start-with-states {#'tapp.websockets/system-a swap-conn})]
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (= 42 (dval system-a)))
(mount/stop)))
(testing "should start normally after start-with-states"
(let [_ (mount/start)]
(is (map? (dval config)))
(is (instance? datascript.db/DB @(dval log)))
(is (instance? js/WebSocket (dval system-a)))
(is (= 42 (dval test-conn)))
(is (vector? (dval test-nrepl)))
(is (= :started (dval helper)))
(mount/stop)))
(testing "should start-without normally after start-with-states"
(let [_ (mount/start-without #'mount.test.start-with-states/test-conn
#'mount.test.start-with-states/test-nrepl)]
(is (map? (dval config)))
(is (instance? datascript.db/DB @(dval log)))
(is (instance? js/WebSocket (dval system-a)))
(is (= :started (dval helper)))
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
(mount/stop)))))
#?(:clj
(deftest start-with-states
(testing "should start with substitutes"
(let [_ (mount/start-with-states {#'tapp.nyse/conn swap-conn
#'tapp.example/nrepl swap-nrepl})]
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (= (dval conn) 42))
(mount/stop)))
#_(testing "should not start the substitute itself" ;; was true when subbing with exsiting states
(let [_ (mount/start-with-states {#'tapp.nyse/conn swap-conn})]
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (= (dval conn) 42))
(mount/stop)))
(testing "should start normally after start-with-states"
(let [_ (mount/start)]
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval test-conn) 42))
(is (vector? (dval test-nrepl)))
(mount/stop)))
(testing "should start-without normally after start-with-states"
(let [_ (mount/start-without #'mount.test.start-with-states/test-conn
#'mount.test.start-with-states/test-nrepl)]
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (instance? mount.core.NotStartedState (dval test-conn)))
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
(mount/stop)))))

View file

@ -20,7 +20,7 @@
(f)
(mount/stop)))
(use-fixtures :once
(use-fixtures :once
#?(:cljs {:before #(mount/start-without #'mount.test.helper/helper #'tapp.websockets/system-a)
:after mount/stop}
:clj without))

View file

@ -25,7 +25,7 @@
(is (instance? mount.core.NotStartedState (dval config)))
(is (instance? mount.core.NotStartedState (dval system-a)))
(mount/stop)))
(testing "should start normally after stop-except"
(let [_ (mount/start)]
(is (map? (dval config)))
@ -51,7 +51,7 @@
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (instance? mount.core.NotStartedState (dval nrepl)))
(mount/stop)))
(testing "should start normally after stop-except"
(let [_ (mount/start)]
(is (map? (dval config)))

View file

@ -1,212 +0,0 @@
(ns mount.test.suspend-resume
(:require
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
[mount.core :as mount :refer-macros [defstate]]
[tapp.websockets :refer [system-a]]
[tapp.conf :refer [config]]
[tapp.audit-log :refer [log]]]
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]
[tapp.conf :refer [config]]
[tapp.nyse :refer [conn]]
[tapp.example :refer [nrepl]]])
[mount.test.helper :refer [dval]]))
#?(:clj (alter-meta! *ns* assoc ::load false))
(defn koncat [k s]
(-> (name k)
(str "-" (name s))
keyword))
(defn start [s] (koncat s :started))
(defn stop [s] (koncat s :stopped))
(defn suspend [s] (koncat s :suspended))
(defn resume [s] (koncat s :resumed))
(defstate web-server :start (start :w)
:stop (stop :w)
:suspend (suspend :w)
:resume (resume :w))
(defstate q-listener :start (start :q)
:stop (stop :q)
:suspend (suspend :q)
:resume (resume :q))
(defstate randomizer :start (rand-int 42))
#?(:cljs
(deftest suspendable-lifecycle
(testing "should suspend _only suspendable_ states that are currently started"
(let [_ (mount/start)
_ (mount/suspend)]
(is (map? (dval config)))
(is (instance? datascript.db/DB @(dval log)))
(is (instance? js/WebSocket (dval system-a)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))
(testing "should resume _only suspendable_ states that are currently suspended"
(let [_ (mount/start)
_ (mount/stop #'tapp.websockets/system-a)
_ (mount/suspend)
_ (mount/resume)]
(is (map? (dval config)))
(is (instance? mount.core.NotStartedState (dval system-a)))
(is (instance? datascript.db/DB @(dval log)))
(is (= (dval web-server) :w-resumed))
(mount/stop)))
(testing "should start all the states, except the ones that are currently suspended, should resume them instead"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/start)]
(is (map? (dval config)))
(is (instance? js/WebSocket (dval system-a)))
(is (instance? datascript.db/DB @(dval log)))
(is (= (dval web-server) :w-resumed))
(mount/stop)))
(testing "should stop all: started and suspended"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/stop)]
(is (instance? mount.core.NotStartedState (dval config)))
(is (instance? mount.core.NotStartedState (dval system-a)))
(is (instance? mount.core.NotStartedState (dval log)))
(is (instance? mount.core.NotStartedState (dval web-server)))))))
#?(:cljs
(deftest suspendable-start-with
(testing "when replacing a non suspendable state with a suspendable one,
the later should be able to suspend/resume,
the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop"
(let [_ (mount/start-with {#'tapp.websockets/system-a #'mount.test.suspend-resume/web-server})
_ (mount/suspend)]
(is (= (dval system-a) :w-suspended))
(is (instance? mount.core.NotStartedState (dval web-server)))
(mount/stop)
(mount/start)
(mount/suspend)
(is (instance? js/WebSocket (dval system-a)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))))
#?(:clj
(deftest suspendable-lifecycle
(testing "should suspend _only suspendable_ states that are currently started"
(let [_ (mount/start)
_ (mount/suspend)]
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))
(testing "should resume _only suspendable_ states that are currently suspended"
(let [_ (mount/start)
_ (mount/stop #'tapp.example/nrepl)
_ (mount/suspend)
_ (mount/resume)]
(is (map? (dval config)))
(is (instance? mount.core.NotStartedState (dval nrepl)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval web-server) :w-resumed))
(mount/stop)))
(testing "should start all the states, except the ones that are currently suspended, should resume them instead"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/start)]
(is (map? (dval config)))
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval web-server) :w-resumed))
(mount/stop)))
(testing "should stop all: started and suspended"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/stop)]
(is (instance? mount.core.NotStartedState (dval config)))
(is (instance? mount.core.NotStartedState (dval nrepl)))
(is (instance? mount.core.NotStartedState (dval conn)))
(is (instance? mount.core.NotStartedState (dval web-server)))))))
#?(:clj
(deftest suspendable-start-with
(testing "when replacing a non suspendable state with a suspendable one,
the later should be able to suspend/resume,
the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop"
(let [_ (mount/start-with {#'tapp.example/nrepl #'mount.test.suspend-resume/web-server})
_ (mount/suspend)]
(is (= (dval nrepl) :w-suspended))
(is (instance? mount.core.NotStartedState (dval web-server)))
(mount/stop)
(mount/start)
(mount/suspend)
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))
;; this is a messy use case, but can still happen especially at REPL time
;; it also messy, because usually :stop function refers the _original_ state by name (i.e. #(disconnect conn))
;; (unchanged/not substituted in its lexical scope), and original state won't be started
(testing "when replacing a suspendable state with a non suspendable one,
the later should not be suspendable,
the original should still be suspendable and preserve its lifecycle fns after the rollback/stop"
(let [_ (mount/start-with {#'mount.test.suspend-resume/web-server #'mount.test.suspend-resume/randomizer})
_ (mount/suspend)]
(is (integer? (dval web-server)))
(is (instance? mount.core.NotStartedState (dval randomizer)))
(mount/stop)
(mount/start)
(mount/suspend)
(is (integer? (dval randomizer)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))
;; this is a messy use case, but can still happen especially at REPL time
(testing "when replacing a suspended state with a non suspendable started one,
the later should not be suspendable,
the original should still be suspended and preserve its lifecycle fns after the rollback/stop"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/start-with {#'mount.test.suspend-resume/web-server #'tapp.nyse/conn}) ;; TODO: good to WARN on started states during "start-with"
_ (mount/suspend)]
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval web-server) :w-suspended)) ;; since the "conn" does not have a resume method, so web-server was not started
(mount/stop)
(mount/start)
(mount/suspend)
(is (instance? datomic.peer.LocalConnection (dval conn)))
(is (= (dval web-server) :w-suspended))
(mount/stop)))
;; this is a messy use case, but can still happen especially at REPL time
(testing "when replacing a suspended state with a suspendable one,
the later should be suspendable,
the original should still be suspended and preserve its lifecycle fns after the rollback/stop"
(let [_ (mount/start)
_ (mount/suspend)
_ (mount/start-with {#'mount.test.suspend-resume/web-server
#'mount.test.suspend-resume/q-listener})] ;; TODO: good to WARN on started states during "start-with"
(is (= (dval q-listener) :q-suspended))
(is (= (dval web-server) :q-resumed))
(mount/suspend)
(is (= (dval q-listener) :q-suspended))
(is (= (dval web-server) :q-suspended))
(mount/stop)
(is (instance? mount.core.NotStartedState (dval web-server)))
(is (instance? mount.core.NotStartedState (dval q-listener)))
(mount/start)
(mount/suspend)
(is (= (dval q-listener) :q-suspended))
(is (= (dval web-server) :w-suspended))
(mount/stop)))))

View file

@ -0,0 +1,29 @@
(ns mount.test-self-host
(:require
[cljs.test :as t]
mount.test.fun-with-values
mount.test.private-fun
mount.test.printing
mount.test.parts
mount.test.cleanup-dirty-states
mount.test.stop-except
mount.test.start-without
mount.test.start-with
mount.test.start-with-states
))
(t/run-tests
'mount.test.fun-with-values
'mount.test.private-fun
'mount.test.printing
'mount.test.parts
'mount.test.cleanup-dirty-states
;; 'mount.test.stop-except ;; TODO: can't run with deps.edn (due to "WebSocket is not defined")
;; 'mount.test.start-with ;; boot, lein have no problems
;; 'mount.test.start-with-states ;; most likely somm misconfigured in node..
'mount.test.start-without
)
(defn run-tests []
(t/run-all-tests #"mount.test.*"))