aide icon indicating copy to clipboard operation
aide copied to clipboard

ClojureScript/Clojure event-driven application framework.

= Aide

ClojureScript/Clojure event-driven application framework. Successor to https://github.com/metametadata/carry/[Carry].

[link=https://clojars.org/aide] image::https://img.shields.io/clojars/v/aide.svg[Clojars Project]

== Why not Carry?

  • Carry framework is very strict about separating signals (side effects) and actions (model changing functions). In a long run it turned out to produce a lot of boilerplate (e.g. many signal handlers are anemic and only dispatch an action). Such separation was mainly needed for implementing Redux-ish time traveling debugger, but in the end I don't find such tool too useful, it's pretty easy to debug the app using traditional approaches.

  • By default Carry recommends using keywords for events and actions. It makes it cumbersome to maintain the codebase, because IDEs don't understand this abstraction.

== Why not re-frame?

re-frame has an implicit global state and hard dependency on Reagent.

Also it's based on patterns which are too heavyweight:

  • Subscriptions (vs., for instance, Reagent reactions or Rum derived atoms).
  • Effects & co-effects (vs. usual function and/or method calls and using mocks in tests).
  • Interceptors (vs. middleware pattern).
  • Async event handling queue.

== Features

  • Events can be defined as vars (so that IDE can jump to definition and compiler can detect undefined events).
  • Event definitions look similarly to defn (so that IDE can highlight arguments, etc.).
  • Events can be async.
  • Events can be serialized/deserialized. E.g. it's possible to record and replay user's actions.
  • No enforced separation between "side-effectful" and "pure" events.
  • Testable. It's possible to test event handlers similarly to usual functions and also mock the events.
  • Supports injecting additional functionality via third-party packages (such as logging, routing, etc.). See https://github.com/metametadata/aide/tree/master/contrib.
  • Proposes a pattern for SPAs (single app atom, view/view-model separation).
  • Framework core is agnostic to UI and model layers.
  • Proposes app lifecycle pattern (via standard start/stop events). This is needed for "stateful" third-party packages.
  • (TODO) Time traveling debugger.

== Status

Alpha (API is a subject to change, needs more examples, docs, etc.). Is used in production.

== Code Examples

Define an app which has some model:

[source, clojure] .... (ns app.core (:require [aide.core :as aide]))

(let [*model (atom {:val 0}) app (aide/object {:*model *model})] ,,,) ....

Define an event which modifies the app model:

[source, clojure] .... (aide/defevent on-increment [app _data] (swap! (:*model app) update :val inc)) ....

Define an event which emits another event asynchronously:

[source, clojure] .... (aide/defevent on-delayed-increment [app delay-ms] (.setTimeout js/window #(aide/emit app on-increment) delay-ms)) ....

Emit events into the app:

[source, clojure] .... (aide/emit app aide-lifecycle/on-start) (aide/emit app on-increment) (aide/emit app on-delayed-increment 1000) (aide/emit app aide-lifecycle/on-stop) ....

Middleware example:

[source, clojure]

(defn add-logging "Will log all events." [object] (update app :aide.core/emit (fn wrap-emit [original-emit] (fn emit [object event data] (println (str event) (pr-str data)) (original-emit object event data)))))

(def app-with-logging (-> app add-logging))

== Integration with Reagent

See https://github.com/metametadata/aide/tree/master/examples.

== Developer Guide

=== Tests (Clojure)

Run tests once: lein test or lein test-refresh :run-once

Autorun tests: lein test-refresh

=== Tests (ClojureScript)

To run tests once:

lein doo phantom test once

or automatically re-run on code changes:

lein doo phantom test auto

== License

Copyright © 2018 Yuri Govorushchenko.

Released under an MIT license.