Compare commits
172 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdff7577a | ||
|
|
984979b347 | ||
|
|
2f4d1a3581 | ||
|
|
2bca9089b8 | ||
|
|
656af38802 | ||
|
|
bfd7adf98f | ||
|
|
08173bc6a1 | ||
|
|
3fc6c58cd6 | ||
|
|
ccaf99910c | ||
|
|
9c588f68c2 | ||
|
|
f16d7004ee | ||
|
|
3caea10121 | ||
|
|
fb09fef496 | ||
|
|
b89b930ec2 | ||
|
|
4065f5df36 | ||
|
|
632977ff41 | ||
|
|
5275023b38 | ||
|
|
ad6ca6fb5e | ||
|
|
eff4c04f21 | ||
|
|
5df6c941f7 | ||
|
|
098b56d14f | ||
|
|
58e2ded430 | ||
|
|
8a3fc385a2 | ||
|
|
2d050e9055 | ||
|
|
b77f504cfd | ||
|
|
5ac52b725b | ||
|
|
c85da6149c | ||
|
|
6cf5390a44 | ||
|
|
bc3924aedf | ||
|
|
5d992042e4 | ||
|
|
83542e56a7 | ||
|
|
232df7a8a3 | ||
|
|
10dbbaa40b | ||
|
|
5b26fb6092 | ||
|
|
e9f9dfca14 | ||
|
|
348297ee11 | ||
|
|
52831f6b3e | ||
|
|
9666d3f372 | ||
|
|
5564e367e7 | ||
|
|
f65ed6a266 | ||
|
|
6dafae195d | ||
|
|
5fab543501 | ||
|
|
2ba3c60995 | ||
|
|
fb52f79396 | ||
|
|
c5f3e4cdf8 | ||
|
|
76e9a71a13 | ||
|
|
8de6b09989 | ||
|
|
884a2b2d87 | ||
|
|
c2687d1b9f | ||
|
|
17112646f1 | ||
|
|
d9c4f621cd | ||
|
|
ed1c9944f6 | ||
|
|
e07d7d6aad | ||
|
|
686b79e03f | ||
|
|
2ff032c8b3 | ||
|
|
244d834c46 | ||
|
|
0ff5fe106c | ||
|
|
bb23747273 | ||
|
|
2d5566ae86 | ||
|
|
1acd4feb4c | ||
|
|
c210cc486c | ||
|
|
4f8384763b | ||
|
|
6e848d1ee4 | ||
|
|
db38d8cacd | ||
|
|
594cc58b71 | ||
|
|
e956dd4de5 | ||
|
|
0a208bd275 | ||
|
|
5e0a4c5bfc | ||
|
|
562340f4dc | ||
|
|
b3f3f49d64 | ||
|
|
a4552fc6ed | ||
|
|
697775ef45 | ||
|
|
75d05e3158 | ||
|
|
dc59f3ec98 | ||
|
|
1ccff026de | ||
|
|
be6e9be246 | ||
|
|
52e360f3ab | ||
|
|
fdb85e0fe2 | ||
|
|
e247636be6 | ||
|
|
e032a6ef54 | ||
|
|
dc7dbdd51e | ||
|
|
677812e3ee | ||
|
|
ac16b04b68 | ||
|
|
14100ad648 | ||
|
|
8bb5e19303 | ||
|
|
fb97606b37 | ||
|
|
a23def2e2f | ||
|
|
e9e107f4fa | ||
|
|
31e0b68918 | ||
|
|
4d961b56c0 | ||
|
|
095eb81488 | ||
|
|
6ae0255677 | ||
|
|
355b9b3ea3 | ||
|
|
2f51e5049e | ||
|
|
f01b0538e1 | ||
|
|
448375d2dc | ||
|
|
a49a7e4d55 | ||
|
|
b873590f0a | ||
|
|
2cface9878 | ||
|
|
2f4ffa47a7 | ||
|
|
436514d407 | ||
|
|
404ea70bfb | ||
|
|
891869721c | ||
|
|
ce5f5ed90a | ||
|
|
94d6d0188a | ||
|
|
9a62688c2f | ||
|
|
736eacdf33 | ||
|
|
17461b1911 | ||
|
|
8f8df230a9 | ||
|
|
7fbb433e2f | ||
|
|
c9945aef5b | ||
|
|
f38bc2f0e7 | ||
|
|
49591d4368 | ||
|
|
62cb9dd516 | ||
|
|
e2ef7bba55 | ||
|
|
087683968d | ||
|
|
f49fceb518 | ||
|
|
5ec4017fdb | ||
|
|
c56bbfcd40 | ||
|
|
a397ae5cc5 | ||
|
|
1c7f2ccc34 | ||
|
|
3218f8afaf | ||
|
|
a0f10407ee | ||
|
|
c189db9d12 | ||
|
|
7d323e727e | ||
|
|
d9ea3a56f0 | ||
|
|
9b063504e8 | ||
|
|
e9ba02b5a5 | ||
|
|
ae6763da8c | ||
|
|
fea36ec2a5 | ||
|
|
0cc94ad1d2 | ||
|
|
d9fd79001d | ||
|
|
8f10b85718 | ||
|
|
1f18cb7067 | ||
|
|
147d9c52fb | ||
|
|
09ccb3fb1d | ||
|
|
f514782716 | ||
|
|
3d767f6256 | ||
|
|
76fb498968 | ||
|
|
e2c73fdc19 | ||
|
|
110f9ee666 | ||
|
|
76cae93f81 | ||
|
|
ccfde46678 | ||
|
|
6ee07a9901 | ||
|
|
307331a4f2 | ||
|
|
35655ed752 | ||
|
|
19bed168c8 | ||
|
|
690f9fc6c8 | ||
|
|
736d93abd1 | ||
|
|
0acb78e16a | ||
|
|
82ec3d34aa | ||
|
|
7ee66db407 | ||
|
|
f4683252df | ||
|
|
b7a1c070bb | ||
|
|
5755b7cc49 | ||
|
|
31f92fd52c | ||
|
|
677aa0cfdc | ||
|
|
e34a164c2e | ||
|
|
afe96e9aad | ||
|
|
f26da26d80 | ||
|
|
69a592cec6 | ||
|
|
7becc38282 | ||
|
|
8920b36597 | ||
|
|
bd2ba3ac6d | ||
|
|
c3ce0c6401 | ||
|
|
dc089c1aec | ||
|
|
78b9a23c71 | ||
|
|
9131cbb17a | ||
|
|
ef4dee6c8a | ||
|
|
c001c19eb5 | ||
|
|
f6e6a92055 | ||
|
|
064c00599c |
55 changed files with 925 additions and 349 deletions
1
.clj-kondo/config.edn
Normal file
1
.clj-kondo/config.edn
Normal file
|
|
@ -0,0 +1 @@
|
|||
{:config-paths ["../resources/clj-kondo.exports/mount/mount/"]}
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -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/*
|
||||
|
|
@ -18,3 +21,4 @@ doo-index.html
|
|||
/.nrepl-history
|
||||
.cljs_rhino_repl/
|
||||
out/
|
||||
.clj-kondo/.cache
|
||||
|
|
|
|||
26
.travis.yml
Normal file
26
.travis.yml
Normal 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
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
|
|
@ -1,7 +1,60 @@
|
|||
## 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))
|
||||
* `: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))
|
||||
|
|
|
|||
31
Makefile
Normal file
31
Makefile
Normal 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
|
||||
189
README.md
189
README.md
|
|
@ -1,22 +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">
|
||||
[](https://github.com/tolitius/mount/releases)
|
||||
[](https://clojars.org/mount)
|
||||
|
||||
module | branch | status
|
||||
----------|----------|----------
|
||||
mount | `master` | [](https://circleci.com/gh/tolitius/mount/tree/master)
|
||||
mount | `0.1.10-SNAPSHOT` | [](https://circleci.com/gh/tolitius/mount/tree/0.1.10-SNAPSHOT)
|
||||
###### _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))
|
||||
|
||||
[](http://clojars.org/mount)
|
||||
|
||||
> <img src="doc/img/slack-icon.png" width="30px"> _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel (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)
|
||||
|
|
@ -37,12 +28,17 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
|||
- [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)
|
||||
|
|
@ -91,15 +87,15 @@ Pull request away, let's solve this thing!
|
|||
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))
|
||||
```
|
||||
|
||||
|
|
@ -107,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"]
|
||||
|
|
@ -124,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
|
||||
|
|
@ -160,8 +172,9 @@ this `config`, being top level, can be used in other namespaces, including the o
|
|||
(defstate conn :start (create-connection config))
|
||||
```
|
||||
|
||||
[here](dev/clj/app/www.clj#L32)
|
||||
is an example of a web server that "depends" on a similar `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
|
||||
|
||||
|
|
@ -274,7 +287,7 @@ Each "tool" has a single responsibility and can be composed with other tools in
|
|||
* `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 as their substitute states
|
||||
* `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.
|
||||
|
|
@ -299,12 +312,12 @@ All of the above is much easier to understand by looking at examples:
|
|||
|
||||
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 demonstraate that both these functions can take any collection of states. `set` would make more sense for most cases though.
|
||||
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
|
||||
(-> (only #{#'foo/a
|
||||
#'foo/b
|
||||
#'foo/c
|
||||
#'bar/d
|
||||
|
|
@ -312,12 +325,13 @@ Here is a more "involved" example:
|
|||
(with-args {:a 42})
|
||||
(except [#'foo/c
|
||||
#'bar/d])
|
||||
(swap-states {#'foo/a #'test/a})
|
||||
(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 `#'test/a` state and `#'baz/e` with `{:datomic {:uri "datomic:mem://composable-mount"}}` value before starting the application.
|
||||
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
|
||||
|
||||
|
|
@ -356,14 +370,14 @@ During testing it is often very useful to mock/stub certain states. For example
|
|||
|
||||
### Swapping States with Values
|
||||
|
||||
The `start-with` function takes values as substitues.
|
||||
The `start-with` function takes values as substitutes.
|
||||
|
||||
Say we have a `send-sms` state:
|
||||
|
||||
```clojure
|
||||
(ns app.sms)
|
||||
;; ...
|
||||
(defstate send-sms :start (create-sms-sender
|
||||
(defstate send-sms :start (create-sms-sender
|
||||
(:sms config)))
|
||||
```
|
||||
|
||||
|
|
@ -381,14 +395,21 @@ When running tests it would be great _not_ to send the real text messages, but r
|
|||
|
||||
### Swapping States with States
|
||||
|
||||
The `start-with-states` function takes other states as substitues:
|
||||
The `start-with-states` function takes values in a form of `{:start fn :stop fn}` as substitutes:
|
||||
|
||||
```clojure
|
||||
(mount/start-with {#'app.neo/db #'app.test/test-db
|
||||
#'app.neo/publisher #'app.test/test-publisher})
|
||||
(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 `#'app.test/test-db` state, which could be anything, a map, an in memory DB, etc.
|
||||
`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.
|
||||
|
||||
--
|
||||
|
||||
|
|
@ -459,28 +480,30 @@ Providing a `:stop` function _is_ optional, but in case a state needs to be clea
|
|||
|
||||
### :on-reload
|
||||
|
||||
By default a state will be restarted on its redefenition 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".
|
||||
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 conrolled with an optional `:on-reload` meta attribute when defining a state.
|
||||
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
|
||||
(defstate ^{:on-reload :noop}
|
||||
mem-db :start (connect config)
|
||||
(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)
|
||||
(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:
|
||||
|
|
@ -491,7 +514,7 @@ Mount will detect when a state was renamed/deleted from a namespace, and will do
|
|||
Here is an example:
|
||||
|
||||
```clojure
|
||||
dev=> (defstate won't-be-here-long :start (println "I am starting... ")
|
||||
dev=> (defstate won't-be-here-long :start (println "I am starting... ")
|
||||
:stop (println "I am stopping... "))
|
||||
#'dev/won't-be-here-long
|
||||
dev=>
|
||||
|
|
@ -525,6 +548,56 @@ Mount detected that `#'dev/won't-be-here-long` was deleted, hence:
|
|||
<< 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.
|
||||
|
|
@ -562,7 +635,7 @@ dev=> (mount/start)
|
|||
```
|
||||
```clojure
|
||||
dev=> (mount/stop)
|
||||
{:started [#'app/nrepl
|
||||
{:stopped [#'app/nrepl
|
||||
#'app.nyse/conn
|
||||
#'app.config/config]}
|
||||
```
|
||||
|
|
@ -577,13 +650,39 @@ Valid question. It was a [conscious choice](https://github.com/tolitius/mount/is
|
|||
|
||||
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 CljoureScript, 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.
|
||||
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!
|
||||
|
||||
|
|
@ -654,7 +753,7 @@ 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)`.
|
||||
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":_
|
||||
|
||||
|
|
@ -703,7 +802,7 @@ dev=> (find-orders conn "TSLA")
|
|||
|
||||
### New York Stock Exchange Maintenance
|
||||
|
||||
Say we want to leave the exchange functioning, but would like to make sure that noone can hit it from the web. Easy, just stop the web server:
|
||||
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)
|
||||
|
|
@ -762,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
2
boot.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
BOOT_VERSION=2.7.1
|
||||
BOOT_CLOJURE_VERSION=1.10.1
|
||||
60
build.boot
60
build.boot
|
|
@ -1,40 +1,56 @@
|
|||
(def +version+ "0.1.10")
|
||||
(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"]
|
||||
[tolitius/boot-check "0.1.1" :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"]
|
||||
[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]]
|
||||
[nrepl "0.4.5" :scope "test"]
|
||||
[pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
|
||||
[tolitius/boot-stripper "0.1.0-SNAPSHOT" :scope "test"]
|
||||
[com.cemerick/piggieback "0.2.1" :scope "test" :exclusions [org.clojure/clojurescript]]
|
||||
[weasel "0.7.0" :scope "test" :exclusions [org.clojure/clojurescript]]
|
||||
[adzerk/boot-reload "0.4.2" :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]
|
||||
|
|
@ -62,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!)
|
||||
|
|
@ -80,7 +96,7 @@
|
|||
|
||||
(require '[mount.core])
|
||||
|
||||
(comp
|
||||
(comp
|
||||
(strip-deps-attr :attr :classifier :value "aot")
|
||||
(tcs/test-cljs ;; :optimizations :advanced
|
||||
:out-file "mount.js")))
|
||||
|
|
@ -88,7 +104,7 @@
|
|||
(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"})))
|
||||
|
||||
|
|
@ -114,7 +130,7 @@
|
|||
(cljs-repl)
|
||||
(cljs :optimizations :none :ids #{"mount"})))
|
||||
|
||||
(deftask cljs-example
|
||||
(deftask cljs-example
|
||||
"mount cljs example"
|
||||
[]
|
||||
(set-env! :source-paths #(conj % "dev/clj" "dev/cljs"))
|
||||
|
|
@ -127,7 +143,9 @@
|
|||
(cljs :optimizations :advanced :ids #{"mount"})))
|
||||
|
||||
(task-options!
|
||||
push {: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)"
|
||||
|
|
|
|||
15
circle.yml
15
circle.yml
|
|
@ -1,15 +0,0 @@
|
|||
machine:
|
||||
java:
|
||||
version: oraclejdk8
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- wget https://github.com/boot-clj/boot-bin/releases/download/2.5.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 do clean, test2junit
|
||||
52
deps.edn
Normal file
52
deps.edn
Normal 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"]}}}
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(ns app.db
|
||||
(ns app.db
|
||||
(:require [mount.core :refer [defstate]]
|
||||
[datomic.api :as d]
|
||||
[clojure.tools.logging :refer [info]]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
(defn find-orders [conn ticker]
|
||||
(let [orders (d/q '[:find ?e :in $ ?ticker
|
||||
:where [?e :order/symbol ?ticker]]
|
||||
:where [?e :order/symbol ?ticker]]
|
||||
(d/db conn) ticker)]
|
||||
(touch conn orders)))
|
||||
|
|
|
|||
|
|
@ -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,30 +6,26 @@
|
|||
|
||||
(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
|
||||
|
|
@ -39,6 +35,9 @@
|
|||
(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)))
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@
|
|||
(GET "/nyse/orders/:ticker" [ticker]
|
||||
(generate-string (find-orders conn ticker)))
|
||||
|
||||
(POST "/nyse/orders" [ticker qty bid offer]
|
||||
(let [order {:ticker ticker
|
||||
:bid (bigdec bid)
|
||||
:offer (bigdec 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}))))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
(ns dev
|
||||
(:require [clojure.pprint :refer [pprint]]
|
||||
[clojure.tools.namespace.repl :as tn]
|
||||
[boot.core :refer [load-data-readers!]]
|
||||
[mount.core :as mount :refer [defstate]]
|
||||
[mount.tools.graph :refer [states-with-deps]]
|
||||
[app.utils.logging :refer [with-logging-status]]
|
||||
|
|
@ -41,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
15
dev/clj/proto_play.clj
Normal 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"))
|
||||
|
|
@ -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])))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
" → [" (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)]))])))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{:datomic
|
||||
{:datomic
|
||||
{:uri "datomic:mem://mount"}
|
||||
|
||||
:www {:port 4242}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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,7 +42,7 @@ Mount has two modes `clj` and `cljc`.
|
|||
|
||||
#### Clojure _and_ ClojureScript Mode
|
||||
|
||||
`cljc` mode is is not default, but it is easy to switch to:
|
||||
`cljc` is not a default mode, but it is easy to switch to:
|
||||
|
||||
To switch Mount into this mode do:
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ To switch Mount into this mode do:
|
|||
|
||||
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
|
||||
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_"
|
||||
|
|
@ -83,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">
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ The not so hidden benefit is REPL time reloadability that it brings to the table
|
|||
- [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 -->
|
||||
|
|
@ -57,7 +55,7 @@ Before moving on to differences, [here](https://news.ycombinator.com/item?id=246
|
|||
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
|
||||
|
|
@ -96,7 +94,7 @@ 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 bounaries".
|
||||
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.
|
||||
|
|
@ -148,48 +146,23 @@ no "ceremony".
|
|||
|
||||
Mount uses namespaces and vars where Component uses records and protocols.
|
||||
|
||||
Component manages protocols and records, and in order to do that it requires a whole app buyin, which makes it a _framework_.
|
||||
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_.
|
||||
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 too: 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 more about mount [packaging](https://github.com/tolitius/mount#packaging) and an [example](uberjar.md#creating-reloadable-uberjarable-app) of building a wepapp uberjar with Mount.
|
||||
|
||||
On the flip side, Component _system_ usually requires all the `:require`s, since in order to be built, it needs to "see" all the top level states.
|
||||
|
||||
###### _conclusion: it's simple in Mount as well, but is left upto developer to require what's needed._
|
||||
|
||||
### Multiple separate systems
|
||||
|
||||
With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which might be 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:
|
||||
|
||||
|
|
@ -197,6 +170,7 @@ 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)
|
||||
* [composing states](https://github.com/tolitius/mount#composing-states)
|
||||
|
||||
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).
|
||||
|
||||
|
|
|
|||
BIN
doc/img/mount-logo.png
Normal file
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 |
23
package.json
Normal file
23
package.json
Normal 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
45
pom.xml
Normal 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>
|
||||
14
project.clj
14
project.clj
|
|
@ -1,4 +1,4 @@
|
|||
(defproject mount "0.1.10"
|
||||
(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")
|
||||
|
||||
|
|
|
|||
3
resources/clj-kondo.exports/mount/mount/config.edn
Normal file
3
resources/clj-kondo.exports/mount/mount/config.edn
Normal 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}}}
|
||||
39
resources/clj-kondo.exports/mount/mount/hooks/defstate.clj
Normal file
39
resources/clj-kondo.exports/mount/mount/hooks/defstate.clj
Normal 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)))))})))
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
(ns mount.core
|
||||
#?(: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 :as macro]
|
||||
:cljs (:require [mount.tools.macro]
|
||||
[clojure.set :refer [intersection]]
|
||||
[mount.tools.logger :refer [log]]))
|
||||
#?(:cljs (:require-macros [mount.core]
|
||||
[mount.tools.macro :refer [if-clj on-error throw-runtime]])))
|
||||
[mount.tools.macro :refer [on-error throw-runtime]]
|
||||
[mount.tools.macrovich :refer [deftime]])))
|
||||
|
||||
(defonce ^:private -args (atom {})) ;; mostly for command line args and external files
|
||||
(defonce ^:private state-seq (atom 0))
|
||||
|
|
@ -15,10 +18,6 @@
|
|||
(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)))
|
||||
|
|
@ -58,17 +57,6 @@
|
|||
(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)
|
||||
|
|
@ -95,34 +83,75 @@
|
|||
(swap! running assoc state {:stop stop})
|
||||
(update-meta! [state :status] #{:started}))))
|
||||
|
||||
(defn- down [state {:keys [stop status] :as current} done]
|
||||
(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})
|
||||
(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
|
||||
(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 running-states []
|
||||
(set (keys @running)))
|
||||
|
||||
(deftype DerefableState [name]
|
||||
#?(:clj clojure.lang.IDeref
|
||||
:cljs IDeref)
|
||||
(#?(: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
|
||||
(defn current-state [state]
|
||||
(let [{:keys [var]} (@meta-state state)]
|
||||
(if (= @mode :cljc)
|
||||
(->DerefableState state)
|
||||
(var-get var))))
|
||||
|
||||
: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
|
||||
|
||||
;;TODO: make private after figuring out the inconsistency betwen cljs compile stages
|
||||
(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))
|
||||
(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)
|
||||
|
|
@ -132,30 +161,42 @@
|
|||
(log (str ">> starting.. " s-name " (namespace was recompiled)"))
|
||||
(up s-name with-inst (atom #{})))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro defstate [state & body]
|
||||
(let [[state params] (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
|
||||
(~'defonce ~state (DerefableState. ~state-name))
|
||||
(mount-it (~'var ~state) ~state-name ~s-meta)
|
||||
(~'var ~state))))))
|
||||
(deftime
|
||||
|
||||
#?(: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!)))))
|
||||
(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,7 +207,7 @@
|
|||
;;TODO args might need more thinking
|
||||
(defn args [] @-args)
|
||||
|
||||
(defn- find-all-states []
|
||||
(defn find-all-states []
|
||||
(keys @meta-state))
|
||||
|
||||
#?(:clj
|
||||
|
|
@ -227,9 +268,7 @@
|
|||
origin (@meta-state state)
|
||||
sub (if (= :value mode)
|
||||
{:start (fn [] with) :status :stopped}
|
||||
(@meta-state with))]
|
||||
(when (= :state mode)
|
||||
(update-meta! [with :sub?] true))
|
||||
(assoc with :status :stopped))]
|
||||
(update-meta! [state] (merge-lifecycles origin (lifecycle-fns origin) sub))))
|
||||
|
||||
(defn- unsub [state]
|
||||
|
|
@ -240,17 +279,27 @@
|
|||
(remove (comp :sub? @meta-state) (find-all-states)))
|
||||
|
||||
(defn start [& states]
|
||||
(if (-> states first coll?)
|
||||
(apply start (first 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
|
||||
|
||||
|
|
@ -259,22 +308,22 @@
|
|||
set))
|
||||
|
||||
(defn only
|
||||
([states]
|
||||
(only (find-all-states) states))
|
||||
([these]
|
||||
(only (find-all-states) these))
|
||||
([states these]
|
||||
(intersection (mapset var-to-str these)
|
||||
(mapset var-to-str states))))
|
||||
|
||||
(defn with-args
|
||||
(defn with-args
|
||||
([args]
|
||||
(with-args (find-all-states) args))
|
||||
([states args]
|
||||
(reset! -args args) ;; TODO localize
|
||||
states))
|
||||
|
||||
(defn except
|
||||
([states]
|
||||
(except (find-all-states) states))
|
||||
(defn except
|
||||
([these]
|
||||
(except (find-all-states) these))
|
||||
([states these]
|
||||
(remove (mapset var-to-str these)
|
||||
(mapset var-to-str states))))
|
||||
|
|
@ -294,16 +343,44 @@
|
|||
([states with]
|
||||
(doseq [[from to] with]
|
||||
(substitute! (var-to-str from)
|
||||
(var-to-str to) :state))
|
||||
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)
|
||||
|
|
@ -320,7 +397,7 @@
|
|||
(defn start-with-states [with]
|
||||
(doseq [[from to] with]
|
||||
(substitute! (var-to-str from)
|
||||
(var-to-str to) :state))
|
||||
to :state))
|
||||
(start))
|
||||
|
||||
(defn start-without [& states]
|
||||
|
|
@ -328,5 +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)))
|
||||
|
|
|
|||
|
|
@ -24,4 +24,3 @@
|
|||
meta-with-ns)
|
||||
states)
|
||||
(sort-by :order)))))
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,18 @@
|
|||
(:import [goog.debug Console])]))
|
||||
|
||||
#?(:cljs
|
||||
(defonce *logger*
|
||||
(defonce ^:dynamic *logger*
|
||||
(do
|
||||
(.setCapturing (Console.) true)
|
||||
(glog/getLogger "mount"))))
|
||||
(glog/getLogger "mount" nil))))
|
||||
|
||||
#?(:clj
|
||||
(defn log [msg]
|
||||
(defn log [msg & _]
|
||||
(prn msg)))
|
||||
|
||||
#?(:cljs
|
||||
(defn log [msg]
|
||||
(glog/info *logger* msg)))
|
||||
(defn log [msg & level]
|
||||
(case (first level)
|
||||
:error (glog/error *logger* msg nil)
|
||||
(glog/info *logger* msg nil))))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +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 if-clj [then else]
|
||||
(if (-> &env :ns not)
|
||||
then
|
||||
else)))
|
||||
(deftime
|
||||
|
||||
#?(:clj
|
||||
(defmacro on-error [msg f]
|
||||
`(if-clj
|
||||
(try ~f
|
||||
(catch Throwable t#
|
||||
(throw (RuntimeException. ~msg t#))))
|
||||
(try ~f
|
||||
(catch :default t#
|
||||
(throw (~'str ~msg " " t#)))))))
|
||||
(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#)})))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro throw-runtime [msg]
|
||||
`(throw (if-clj (RuntimeException. ~msg)
|
||||
(~'str ~msg)))))
|
||||
(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
|
||||
|
|
|
|||
21
src/mount/tools/macrovich.cljc
Normal file
21
src/mount/tools/macrovich.cljc
Normal 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)))
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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,30 +6,26 @@
|
|||
|
||||
(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
|
||||
|
|
@ -37,7 +33,10 @@
|
|||
#'mount.core/down})
|
||||
|
||||
(defn without-logging-status []
|
||||
(doall (map clear-hooks lifecycle-fns)))
|
||||
(doall (map #(clear-hooks %) lifecycle-fns)))
|
||||
|
||||
|
||||
;; this is the one to use:
|
||||
|
||||
(defn with-logging-status []
|
||||
(without-logging-status)
|
||||
|
|
|
|||
|
|
@ -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])))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
" → [" (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)]))])))
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
mount.test.start-without
|
||||
mount.test.start-with
|
||||
mount.test.start-with-states
|
||||
mount.test.printing
|
||||
))
|
||||
|
||||
#?(:clj (alter-meta! *ns* assoc ::load false))
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
'mount.test.start-without
|
||||
'mount.test.start-with
|
||||
'mount.test.start-with-states
|
||||
'mount.test.printing
|
||||
))
|
||||
|
||||
(defn run-tests []
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
[mount.test.helper :refer [dval helper forty-two]]))
|
||||
|
||||
(def status (atom :a-not-started))
|
||||
(defstate a :start (reset! status :a-started)
|
||||
(defstate a :start (reset! status :a-started)
|
||||
:stop (reset! status :a-stopped))
|
||||
|
||||
#?(:clj (alter-meta! *ns* assoc ::load false))
|
||||
|
|
@ -30,11 +30,11 @@
|
|||
(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"}
|
||||
(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"}
|
||||
(is (not (some #{"#'mount.test.cleanup-deleted-states/a"}
|
||||
(-> (mount/stop) :stopped set))))))
|
||||
|
||||
;; (t/run-tests)
|
||||
|
|
|
|||
|
|
@ -48,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))))))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
[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]]
|
||||
|
|
@ -21,17 +22,23 @@
|
|||
|
||||
(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.
|
||||
(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"}
|
||||
(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 (= #{"#'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
|
||||
|
|
@ -93,19 +100,29 @@
|
|||
(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 #'mount.test.composable-fns/test-conn
|
||||
#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})]
|
||||
(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 (vector? (dval nrepl)))
|
||||
(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 #'mount.test.composable-fns/test-conn
|
||||
#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})]
|
||||
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)))
|
||||
|
|
@ -127,14 +144,14 @@
|
|||
(with-args {:a 42})
|
||||
(except [#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn])
|
||||
(swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})
|
||||
(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 (vector? (dval nrepl)))
|
||||
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should compose and start in a single composition"
|
||||
|
|
@ -147,11 +164,27 @@
|
|||
(with-args {:a 42})
|
||||
(except [#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn])
|
||||
(swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})
|
||||
(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 (vector? (dval nrepl)))
|
||||
(mount/stop)))))
|
||||
(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)))))))
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
[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]])
|
||||
[tapp.example]])
|
||||
[mount.test.helper :refer [dval helper forty-two counter inc-counter]]
|
||||
[mount.test.on-reload-helper :refer [a b c]]))
|
||||
|
||||
|
|
@ -35,20 +35,21 @@
|
|||
(require 'mount.test.on-reload-helper :reload)
|
||||
|
||||
;; "a" is marked as :noop on reload
|
||||
(is (instance? mount.core.NotStartedState (dval a))) ;; (!) stale reference of old a is still there somewhere
|
||||
(is (= (-> pre-reload :a)
|
||||
;; 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)
|
||||
(is (= (-> pre-reload :b :started)
|
||||
(-> @counter :b :started)))
|
||||
(is (= (inc (-> pre-reload :b :stopped))
|
||||
(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))
|
||||
(is (= (inc (-> pre-reload :c :stopped))
|
||||
(-> @counter :c :stopped))))))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
17
test/core/mount/test/printing.cljc
Normal file
17
test/core/mount/test/printing.cljc
Normal 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)))
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
(is (instance? mount.core.NotStartedState (dval test-conn)))
|
||||
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
|
||||
(mount/stop)))))
|
||||
|
||||
|
||||
#?(:clj
|
||||
(deftest start-with
|
||||
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
(is (= :nrepl-sub (dval nrepl)))
|
||||
(is (= "conn-sub" (dval conn)))
|
||||
(mount/stop)))
|
||||
|
||||
|
||||
(testing "should start normally after start-with"
|
||||
(let [_ (mount/start)]
|
||||
(is (map? (dval config)))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
[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]]])
|
||||
|
|
@ -19,20 +20,29 @@
|
|||
|
||||
(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 #'mount.test.start-with-states/test-conn
|
||||
#'mount.test.helper/helper #'mount.test.start-with-states/test-nrepl})]
|
||||
(let [_ (mount/start-with-states {#'tapp.websockets/system-a swap-conn
|
||||
#'mount.test.helper/helper swap-nrepl})]
|
||||
(is (map? (dval config)))
|
||||
(is (vector? (dval helper)))
|
||||
(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"
|
||||
(let [_ (mount/start-with-states {#'tapp.websockets/system-a #'mount.test.start-with-states/test-conn})]
|
||||
#_(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)))
|
||||
|
|
@ -57,20 +67,20 @@
|
|||
(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 #'mount.test.start-with-states/test-conn
|
||||
#'tapp.example/nrepl #'mount.test.start-with-states/test-nrepl})]
|
||||
(let [_ (mount/start-with-states {#'tapp.nyse/conn swap-conn
|
||||
#'tapp.example/nrepl swap-nrepl})]
|
||||
(is (map? (dval config)))
|
||||
(is (vector? (dval nrepl)))
|
||||
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||
(is (= (dval conn) 42))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should not start the substitute itself"
|
||||
(let [_ (mount/start-with-states {#'tapp.nyse/conn #'mount.test.start-with-states/test-conn})]
|
||||
|
||||
#_(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)))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
29
test/core/mount/test_self_host.cljs
Normal file
29
test/core/mount/test_self_host.cljs
Normal 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.*"))
|
||||
Loading…
Reference in a new issue