Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to ClojureScript #10

Merged
merged 5 commits into from
Nov 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ pom.xml
pom.xml.asc
/.lein-*
/.nrepl-port
/.cpcache
/.cljs
/node_modules
/package*
/.lumo_cache/
33 changes: 25 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ for common async libraries.

> Noun
> **Siepata (Intercept)**
>
>
> sieppari, _someone or something that intercepts_

## What it does
Expand Down Expand Up @@ -34,8 +34,8 @@ If you are familiar with interceptors you might want to jump to `Differences to
(defn handler [request]
{:y (inc (:x request))})

(sieppari/execute
[inc-x-interceptor handler]
(sieppari/execute
[inc-x-interceptor handler]
{:x 40})
;=> {:y 42}
```
Expand Down Expand Up @@ -145,13 +145,13 @@ Requires dependency to `[funcool/promesa "1.9.0"]` or higher.
# Performance

_Sieppari_ aims for minimal functionality and can therefore be
quite fast. Complete example to test performance is
quite fast. Complete example to test performance is
[included](https://github.com/metosin/sieppari/blob/develop/examples/example/perf_testing.clj).

The example creates a chain of 100 interceptors that have
The example creates a chain of 100 interceptors that have
`clojure.core/identity` as `:enter` and `:leave` functions and then
executes the chain. The async tests also have 100 interceptors, but
in async case they all return `core.async` channels on enter and leave.
in async case they all return `core.async` channels on enter and leave.

| Executor | Execution time lower quantile |
| ----------------- | ----------------------------- |
Expand All @@ -176,8 +176,8 @@ in async case they all return `core.async` channels on enter and leave.
* In _Pedestal_ the `error` handler takes two arguments, the `ctx` and the exception.
* In _Sieppari_ the `error` handlers takes just one argument, the `ctx`, and the exception is in the `ctx` under the key `:error`.
* In _Pedestal_ the `error` handler resolves the exception by returning the `ctx`, and continues the **error** stage by re-throwing the exception.
* In _Sieppari_ the `error` handler resolves the exception by returning the `ctx` with the `:error` removed. To continue in the **error** stage, just return the `ctx` with the exception still at `:error`.
* In _Pedestal_ the exception are wrapped in other exceptions.
* In _Sieppari_ the `error` handler resolves the exception by returning the `ctx` with the `:error` removed. To continue in the **error** stage, just return the `ctx` with the exception still at `:error`.
* In _Pedestal_ the exception are wrapped in other exceptions.
* In _Sieppari_ exceptions are not wrapped.
* _Pedestal_ interception execution catches `java.lang.Throwable` for error processing. _Sieppari_ catches `java.lang.Exception`. This means that things like out of memory or class loader failures are not captured by _Sieppari_.

Expand All @@ -186,6 +186,23 @@ in async case they all return `core.async` channels on enter and leave.
* _Pedestal_ transfers thread local bindings from call-site into async interceptors.
* _Sieppari_ does not support this.

### REPL

In order to start a node figwheel REPL for local development use:

```shell
clojure -A:fig:test-cljs:nrepl
```

Then in the REPL:

```
user=> (require 'figwheel.main.api)
nil
user=> (figwheel.main.api/start "dev")
...
```

# Thanks

* Original idea from [Pedestal Interceptors](https://github.com/pedestal/pedestal/tree/master/interceptor).
Expand Down
25 changes: 25 additions & 0 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{:paths ["src"]
:deps {}
:aliases {:test-cljs {:extra-paths ["test/cljs" "test/cljc"]
:extra-deps {org.clojure/clojure {:mvn/version "1.9.0"}
org.clojure/clojurescript {:mvn/version "1.10.339"}

;; Add-ons:
org.clojure/core.async {:mvn/version "0.4.474"}
funcool/promesa {:mvn/version "1.10.0-SNAPSHOT"}}}

:test-clj {:extra-paths ["test/clj" "test/cljc"]
:extra-deps {org.clojure/clojure {:mvn/version "1.9.0"}
;; Add-ons:
org.clojure/core.async {:mvn/version "0.4.474"}
manifold {:mvn/version "0.1.8"}
funcool/promesa {:mvn/version "1.10.0-SNAPSHOT"}
;; Dev:
org.clojure/tools.namespace {:mvn/version "0.2.11"}
;; Testing:
eftest {:mvn/version "0.5.2"}
metosin/testit {:mvn/version "0.4.0-SNAPSHOT"}}}

:self-host {:extra-deps {andare {:mvn/version "0.9.0" :exclusions [org.clojure/clojure org.clojure/clojurescript]}}}

:fig {:extra-deps {com.bhauman/figwheel-main {:mvn/version "0.1.9"}}}}}
9 changes: 9 additions & 0 deletions dev.cljs.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{:main sieppari.core
:target :nodejs
:optimizations :none
:output-dir ".cljs"
:output-to ".cljs/sieppari.js"
:source-map true
:source-map-timestamp false
:install-deps true
:npm-deps {"source-map-support" "0.5.9"}}
27 changes: 27 additions & 0 deletions dev/lumo_runner.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
(ns sieppari.lumo-runner
(:require [cljs.test :as test :refer-macros [run-tests]]
[lumo.core :as lumo]
sieppari.context-test
sieppari.queue-test
sieppari.interceptor-test
sieppari.core-execute-test
;; sieppari.promesa-test ;; not self-host compatible yet
;; sieppari.async.promesa-test
sieppari.native-promise-test
sieppari.core-async-test
sieppari.async.core-async-test))

(enable-console-print!)

(defmethod test/report [:cljs.test/default :end-run-tests] [m]
(when-not (test/successful? m)
(lumo/exit 1)))

(run-tests
'sieppari.context-test
'sieppari.queue-test
'sieppari.interceptor-test
'sieppari.core-execute-test
'sieppari.native-promise-test
'sieppari.core-async-test
'sieppari.async.core-async-test)
3 changes: 2 additions & 1 deletion examples/example/simple.clj → examples/example/simple.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
inc-interceptor
handler])

(s/execute chain {:x 20})
#?(:clj (s/execute chain {:x 20})
:cljs (s/execute chain {:x 20} prn prn))
;=> {:y 42}
4 changes: 2 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

:dependencies []

:profiles {:dev {:source-paths ["dev"]
:profiles {:dev {:source-paths ["dev" "test/clj" "test/cljc"]
:dependencies [[org.clojure/clojure "1.9.0" :scope "provided"]
;; Add-ons:
[org.clojure/core.async "0.4.474"]
[manifold "0.1.8"]
[funcool/promesa "1.9.0"]
[funcool/promesa "1.10.0-SNAPSHOT"]
arichiardi marked this conversation as resolved.
Show resolved Hide resolved
;; Dev:
[org.clojure/tools.namespace "0.2.11"]
;; Testing:
Expand Down
7 changes: 7 additions & 0 deletions scripts/test-cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

set -euo pipefail

clojure -A:fig:test-cljs -m figwheel.main \
-co dev.cljs.edn \
-m sieppari.runner \
7 changes: 7 additions & 0 deletions scripts/test-self-host
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

set -euo pipefail

cp="src:test/cljs:test/cljc:$(clojure -Srepro -A:self-host -Spath)"

lumo -K -c "$cp" dev/lumo_runner.cljs
20 changes: 0 additions & 20 deletions src/sieppari/async.clj

This file was deleted.

23 changes: 23 additions & 0 deletions src/sieppari/async.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(ns sieppari.async
#?(:clj (:refer-clojure :exclude [await])))

(defprotocol AsyncContext
(continue [t f])
#?(:clj (await [t])))

(defn async?
[x]
(satisfies? AsyncContext x))

#?(:clj
(extend-protocol AsyncContext
clojure.lang.IDeref
(continue [c f] (let [p (promise)]
(future (p (f @c)))
p))
(await [c] @c)))

#?(:cljs
(extend-protocol AsyncContext
js/Promise
(continue [t f] (.then t f))))
9 changes: 0 additions & 9 deletions src/sieppari/async/core_async.clj

This file was deleted.

11 changes: 11 additions & 0 deletions src/sieppari/async/core_async.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(ns sieppari.async.core-async
(:require [sieppari.async :as sa]
[clojure.core.async :as cca
#?@(:clj [:refer [go <! <!!]]
:cljs [:refer-macros [go]])]))

(extend-protocol sa/AsyncContext
#?(:clj clojure.core.async.impl.protocols.Channel
:cljs cljs.core.async.impl.channels/ManyToManyChannel)
(continue [c f] (go (f (cca/<! c))))
#?(:clj (await [c] (<!! c))))
26 changes: 0 additions & 26 deletions src/sieppari/async/ext_lib_support.clj

This file was deleted.

27 changes: 27 additions & 0 deletions src/sieppari/async/ext_lib_support.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
(ns sieppari.async.ext-lib-support)

#?(:clj
(defmacro available?
"Try to require a namespace of external library, return
`true` if successful."
[ext-lib-ns]
`(try
(require ~ext-lib-ns)
true
(catch Exception ~'_
false))))

#?(:clj
;; Load support for core-async
(when (available? 'clojure.core.async)
(require 'sieppari.async.core-async)))

#?(:clj
;; Load support for Manifold
(when (available? 'manifold.deferred)
(require 'sieppari.async.manifold)))

#?(:clj
;; Load support for Promesa
(when (available? 'promesa.core)
(require 'sieppari.async.promesa)))
1 change: 0 additions & 1 deletion src/sieppari/async/manifold.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@

(extend-protocol sa/AsyncContext
manifold.deferred.Deferred
(async? [_] true)
(continue [d f] (d/chain'- nil d f))
(await [d] (deref d)))
10 changes: 0 additions & 10 deletions src/sieppari/async/promesa.clj

This file was deleted.

16 changes: 16 additions & 0 deletions src/sieppari/async/promesa.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(ns sieppari.async.promesa
(:require [sieppari.async :as sa]
[promesa.core :as p]
[clojure.string :as str])
#?(:clj (:import (java.util.concurrent CompletionStage))))

#?(:clj
(extend-protocol sa/AsyncContext
CompletionStage
(continue [this f] (p/chain this f))
(await [this] (deref this))))

#?(:cljs
(extend-protocol sa/AsyncContext
p/Promise
(continue [this f] (p/chain this f))))
7 changes: 3 additions & 4 deletions src/sieppari/context.clj → src/sieppari/context.cljc
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
(ns sieppari.context
(:require [sieppari.queue :as q])
(:import (clojure.lang PersistentQueue)))
(:require [sieppari.queue :as q]))

(defn terminate
"Removes all remaining interceptors from context's execution queue.
This effectively short-circuits execution of Interceptors' :enter
functions and begins executing the :leave functions.
Two arity version allows setting the response at the same call."
([ctx]
(assoc ctx :queue PersistentQueue/EMPTY))
(assoc ctx :queue q/empty-queue))
([ctx response]
(-> ctx
(assoc :queue PersistentQueue/EMPTY)
(assoc :queue q/empty-queue)
(assoc :response response))))

(defn inject
Expand Down
Loading