>.web.views.disp.home)
2 |
3 | (defn input [label name value]
4 | [:div
5 | [:label label]
6 | [:input
7 | {:type "text" :name name :value value
8 | :hx-post "bmi-form" :hx-trigger "keyup changed delay:0.3s"}]])
9 |
10 | (defn bmi-label [bmi]
11 | [:label#bmi (format "BMI: %.1f" bmi)])
12 |
13 | (defn form [height weight bmi]
14 | [:div
15 | [:h2 "BMI Calculator"]
16 | (input "height (KG) " "height" height)
17 | (input "weight (M) " "weight" weight)
18 | (bmi-label bmi)])
19 |
--------------------------------------------------------------------------------
/html/assets/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Something Bad Happened
5 |
6 |
7 |
8 |
9 |
10 |
Error: {{status}}
11 |
12 | {% if title %}
13 | {{title}}
14 | {% endif %}
15 | {% if message %}
16 | {{message}}
17 | {% endif %}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/dominoui-postgres/assets/sql/queries.sql:
--------------------------------------------------------------------------------
1 | -- Place your queries here. Docs available https://www.hugsql.org/
2 |
3 | -- :name user-by-id :query :one
4 | select * from user_info where user_id = :user-id
5 |
6 | -- :name set-weight :execute
7 | insert into user_info (user_id, weight)
8 | values (:user-id, :weight)
9 | on conflict (user_id) do update
10 | set weight = EXCLUDED.weight;
11 |
12 | -- :name set-height :execute
13 | insert into user_info (user_id, height)
14 | values (:user-id, :height)
15 | on conflict (user_id) do update
16 | set height = EXCLUDED.height;
17 |
--------------------------------------------------------------------------------
/html/assets/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to <>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
Hello World!
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/cljs/assets/shadow-cljs.edn:
--------------------------------------------------------------------------------
1 | {:nrepl {:port 7002}
2 | :source-paths ["src/cljs"]
3 | :dependencies [[binaryage/devtools "1.0.3"]
4 | [nrepl "0.8.3"]
5 | [cider/cider-nrepl "0.30.0"]
6 | [cljs-ajax "0.8.4"]]
7 | :builds {:app {:target :browser
8 | :output-dir "target/classes/cljsbuild/public/js"
9 | :asset-path "/js"
10 | :modules {:app {:entries [<>.core]
11 | :init-fn <>.core/init!}}
12 | :devtools {:after-load <>.core/mount-root}}}}
13 |
--------------------------------------------------------------------------------
/simpleui/assets/src/hello.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.views.hello
2 | (:require
3 | [simpleui.core :as simpleui :refer [defcomponent]]
4 | [<>.web.htmx :refer [page-htmx]]))
5 |
6 | (defcomponent ^:endpoint hello [req my-name]
7 | [:div#hello "Hello " my-name])
8 |
9 | (defn ui-routes [base-path]
10 | (simpleui/make-routes
11 | base-path
12 | (fn [req]
13 | (page-htmx
14 | [:label {:style "margin-right: 10px"}
15 | "What is your name?"]
16 | [:input {:type "text"
17 | :name "my-name"
18 | :hx-patch "hello"
19 | :hx-target "#hello"
20 | :hx-swap "outerHTML"}]
21 | (hello req "")))))
22 |
--------------------------------------------------------------------------------
/devcontainer/assets/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM clojure:temurin-20-tools-deps-jammy
2 | # Details as of 2023-09-19:
3 | # Ubuntu: Ubuntu 22.04 LTS (Jammy Jellyfish)
4 | # JDK: eclipse-temurin 20
5 | # Clojure: tools-deps, 1.11.1.1347
6 |
7 | # Extra tools
8 | RUN apt-get update && apt-get install -y gpg curl
9 |
10 | # Install new and clj-new (prefer new, but clj-new is needed for some templates)
11 |
12 | RUN clojure -Ttools install-latest :lib io.github.seancorfield/deps-new :as new
13 | RUN clojure -Ttools install-latest :lib com.github.seancorfield/clj-new :as clj-new
14 |
15 | # Add Babashka
16 |
17 | RUN curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install \
18 | && chmod +x install \
19 | && ./install --static
--------------------------------------------------------------------------------
/auth/assets/src/auth.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.middleware.auth
2 | (:require
3 | [buddy.auth.backends.session :as session]
4 | [buddy.auth :as auth]
5 | [buddy.auth.accessrules :as accessrules]
6 | [buddy.auth.middleware :as auth-middleware]))
7 |
8 | (defn on-error [request _response]
9 | {:status 403
10 | :headers {}
11 | :body (str "Access to " (:uri request) " is not authorized")})
12 |
13 | (defn wrap-restricted [handler]
14 | (accessrules/restrict handler {:handler auth/authenticated?
15 | :on-error on-error}))
16 |
17 | (defn wrap-auth [handler]
18 | (let [backend (session/session-backend)]
19 | (-> handler
20 | (auth-middleware/wrap-authentication backend)
21 | (auth-middleware/wrap-authorization backend))))
22 |
--------------------------------------------------------------------------------
/htmx/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets
5 | [["assets/src/ui.clj" "src/clj/<>/web/routes/ui.clj"]
6 | ["assets/src/htmx.clj" "src/clj/<>/web/htmx.clj"]]
7 | :injections
8 | [{:type :edn
9 | :path "resources/system.edn"
10 | :target []
11 | :action :merge
12 | :value {:reitit.routes/ui
13 | {:base-path ""
14 | :env #ig/ref :system/env}}}
15 | {:type :edn
16 | :path "deps.edn"
17 | :target [:deps]
18 | :action :merge
19 | :value {hiccup/hiccup {:mvn/version "2.0.0"}}}
20 | {:type :clj
21 | :path "src/clj/<>/core.clj"
22 | :action :append-requires
23 | :value ["[<>.web.routes.ui]"]}]}}}
24 |
--------------------------------------------------------------------------------
/simpleui/assets/src/htmx.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.htmx
2 | (:require
3 | [simpleui.render :as render]
4 | [ring.util.http-response :as http-response]
5 | [hiccup.core :as h]
6 | [hiccup.page :as p]))
7 |
8 | (defn page [opts & content]
9 | (-> (p/html5 opts content)
10 | http-response/ok
11 | (http-response/content-type "text/html")))
12 |
13 | (defn ui [opts & content]
14 | (-> (h/html opts content)
15 | http-response/ok
16 | (http-response/content-type "text/html")))
17 |
18 | (defn page-htmx [& body]
19 | (page
20 | [:head
21 | [:meta {:charset "UTF-8"}]
22 | [:title "Htmx + Kit"]
23 | [:script {:src "https://unpkg.com/htmx.org@1.2.0/dist/htmx.min.js" :defer true}]]
24 | [:body (render/walk-attrs body)]))
25 |
--------------------------------------------------------------------------------
/devcontainer/assets/docker/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kit devcontainer",
3 | "build": {
4 | "dockerfile": "Dockerfile.dev"
5 | },
6 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
7 | // More info: https://containers.dev/features.
8 | "features": {},
9 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
10 | // "forwardPorts": [ ],
11 | // Run commands on cluster first start. Useful for download dependencies, etc.
12 | // "postCreateCommand": "",
13 | // Copy host env vars into the devcontainer. You can also refer to some_file.env files in your docker or docker-compose setup.
14 | "remoteEnv": {},
15 | "customizations": {
16 | "vscode": {
17 | "extensions": [
18 | "betterthantomorrow.calva"
19 | ]
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/devcontainer/assets/compose/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kit devcontainer",
3 | "dockerComposeFile": "docker-compose.yml",
4 | "service": "devcontainer",
5 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
6 | // More info: https://containers.dev/features.
7 | "features": {},
8 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
9 | // "forwardPorts": [ ],
10 | // Run commands on cluster first start. Useful for download dependencies, etc.
11 | // "postCreateCommand": "",
12 | // Copy host env vars into the devcontainer. You can also refer to some_file.env files in your docker or docker-compose setup.
13 | "remoteEnv": {},
14 | "customizations": {
15 | "vscode": {
16 | "extensions": [
17 | "betterthantomorrow.calva"
18 | ]
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/nrepl/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets []
5 | :injections [{:type :edn
6 | :path "deps.edn"
7 | :target [:deps]
8 | :action :merge
9 | :value {io.github.kit-clj/kit-nrepl {:mvn/version "1.0.4"}}}
10 | {:type :edn
11 | :path "resources/system.edn"
12 | :target []
13 | :action :merge
14 | :value {:nrepl/server {:port #long #or [#env NREPL_PORT 7001]
15 | :bind #or [#env NREPL_HOST "127.0.0.1"]}}}
16 | {:type :clj
17 | :path "src/clj/<>/core.clj"
18 | :action :append-requires
19 | :value ["[kit.edge.utils.nrepl]"]}]}}}
20 |
21 |
--------------------------------------------------------------------------------
/dominoui/assets/src/htmx.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.htmx
2 | (:require
3 | [simpleui.render :as render]
4 | [ring.util.http-response :as http-response]
5 | [hiccup.core :as h]
6 | [hiccup.page :as p]))
7 |
8 | (defn page [opts & content]
9 | (-> (p/html5 opts content)
10 | http-response/ok
11 | (http-response/content-type "text/html")))
12 |
13 | (defn ui [opts & content]
14 | (-> (h/html opts content)
15 | http-response/ok
16 | (http-response/content-type "text/html")))
17 |
18 | (defn page-htmx [& body]
19 | (page
20 | [:head
21 | [:meta {:charset "UTF-8"}]
22 | [:title "DominoUI + Kit"]]
23 | [:body {:hx-ext "ws" :ws-connect "/ws"}
24 | (render/walk-attrs body)
25 | [:script {:src "https://unpkg.com/htmx.org@1.9.9"}]
26 | [:script {:src "https://unpkg.com/htmx.org@1.9.9/dist/ext/ws.js"}]]))
27 |
--------------------------------------------------------------------------------
/datomic/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets [["assets/src/datomic.clj" "src/clj/<>/datomic.clj"]]
5 | :injections
6 | [{:action :merge
7 | :path "resources/system.edn"
8 | :target []
9 | :type :edn
10 | :value
11 | {:db.datomic/conn
12 | #profile
13 | {:dev {:db-uri #or [#env DATOMIC_DB_URI "datomic:dev://localhost:4334/<>"]}
14 | :test {:db-uri #or [#env DATOMIC_DB_URI_TEST "datomic:dev://localhost:4334/<>_test"]}
15 | :prod {:db-uri #env DATOMIC_DB_URI}}}}
16 |
17 | {:action :merge
18 | :path "deps.edn"
19 | :target [:deps]
20 | :type :edn
21 | :value {com.datomic/peer {:mvn/version "1.0.7075"}}}
22 |
23 | {:action :append-requires
24 | :path "src/clj/<>/core.clj"
25 | :type :clj
26 | :value ["[<>.datomic]"]}]}}}
27 |
--------------------------------------------------------------------------------
/dominoui-postgres/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :requires [[:kit/sql {:feature-flag :postgres}] :kit/dominoui]
4 | :actions
5 | {:assets [["assets/migrations/20240104143537-user-info.up.sql" "resources/migrations/20240104143537-user-info.up.sql"]
6 | ["assets/migrations/20240104143537-user-info.down.sql" "resources/migrations/20240104143537-user-info.down.sql"]
7 | ["assets/sql/queries.sql" "resources/sql/queries.sql" :force]
8 | ["assets/src/user.clj" "src/clj/<>/web/controllers/user.clj"]
9 | ["assets/src/domino.clj" "src/clj/<>/web/domino.clj" :force]
10 | ["assets/src/home.clj" "src/clj/<>/web/views/home.clj" :force]]
11 | :injections
12 | [{:type :edn
13 | :path "resources/system.edn"
14 | :target [:reitit.routes/ui]
15 | :action :merge
16 | :value {:query-fn #ig/ref :db.sql/query-fn}}]}}}
17 |
--------------------------------------------------------------------------------
/dominoui/assets/src/home.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.views.home
2 | (:require
3 | [<>.web.domino :as domino]
4 | [<>.web.htmx :refer [page-htmx]]
5 | [<>.web.views.disp.home :as disp.home]
6 | [simpleui.core :as simpleui :refer [defcomponent]]))
7 |
8 | (defcomponent ^:endpoint bmi-form [{:keys [session] :as req} ^:double height ^:double weight]
9 | (cond
10 | height (domino/transact session :height height)
11 | weight (domino/transact session :weight weight)
12 | :else (disp.home/form
13 | (domino/select session :height)
14 | (domino/select session :weight)
15 | (domino/select session :bmi))))
16 |
17 | (defn ui-routes [_]
18 | (simpleui/make-routes
19 | ""
20 | (fn [req]
21 | (let [session (or (not-empty (:session req)) domino/initial-session)
22 | req (assoc req :session session)]
23 | (-> req bmi-form page-htmx (assoc :session session))))))
24 |
25 |
--------------------------------------------------------------------------------
/hato/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets []
5 | :injections [{:type :edn
6 | :path "deps.edn"
7 | :target [:deps]
8 | :action :merge
9 | :value {io.github.kit-clj/kit-hato {:mvn/version "1.0.3"}}}
10 | {:type :clj
11 | :path "src/clj/<>/core.clj"
12 | :action :append-requires
13 | :value ["[kit.edge.http.hato]"]}
14 | {:type :edn
15 | :path "resources/system.edn"
16 | :target []
17 | :action :merge
18 | :value {:http.client/hato {}}}
19 | {:type :edn
20 | :path "resources/system.edn"
21 | :target [:reitit.routes/api]
22 | :action :merge
23 | :value {:http/client #ig/ref :http.client/hato}}]}}}
24 |
--------------------------------------------------------------------------------
/simpleui/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets
5 | [["assets/src/ui.clj" "src/clj/<>/web/routes/ui.clj"]
6 | ["assets/src/htmx.clj" "src/clj/<>/web/htmx.clj"]
7 | ["assets/src/hello.clj" "src/clj/<>/web/views/hello.clj"]]
8 | :injections
9 | [{:type :edn
10 | :path "resources/system.edn"
11 | :target []
12 | :action :merge
13 | :value {:reitit.routes/ui
14 | {:base-path ""
15 | :env #ig/ref :system/env}}}
16 | {:type :edn
17 | :path "deps.edn"
18 | :target [:deps]
19 | :action :merge
20 | :value {simpleui/simpleui {:git/url "https://github.com/whamtet/simpleui"
21 | :git/sha "a107d7a8a69755dae6cd9954992614a86504c257"}}}
22 | {:type :clj
23 | :path "src/clj/<>/core.clj"
24 | :action :append-requires
25 | :value ["[<>.web.routes.ui]"]}]}}}
26 |
--------------------------------------------------------------------------------
/simpleui/assets/src/ui.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.routes.ui
2 | (:require
3 | [<>.web.middleware.exception :as exception]
4 | [<>.web.middleware.formats :as formats]
5 | [<>.web.views.hello :as hello]
6 | [integrant.core :as ig]
7 | [reitit.ring.middleware.muuntaja :as muuntaja]
8 | [reitit.ring.middleware.parameters :as parameters]))
9 |
10 | (defn route-data [opts]
11 | (merge
12 | opts
13 | {:muuntaja formats/instance
14 | :middleware
15 | [;; Default middleware for ui
16 | ;; query-params & form-params
17 | parameters/parameters-middleware
18 | ;; encoding response body
19 | muuntaja/format-response-middleware
20 | ;; exception handling
21 | exception/wrap-exception]}))
22 |
23 | (derive :reitit.routes/ui :reitit/routes)
24 |
25 | (defmethod ig/init-key :reitit.routes/ui
26 | [_ {:keys [base-path]
27 | :or {base-path ""}
28 | :as opts}]
29 | [base-path (route-data opts) (hello/ui-routes base-path)])
30 |
--------------------------------------------------------------------------------
/dominoui-postgres/assets/src/home.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.views.home
2 | (:require
3 | [<>.web.domino :as domino]
4 | [<>.web.htmx :refer [page-htmx]]
5 | [<>.web.views.disp.home :as disp.home]
6 | [simpleui.core :as simpleui :refer [defcomponent]]))
7 |
8 | (defcomponent ^:endpoint bmi-form [{:keys [session] :as req} ^:double height ^:double weight]
9 | (cond
10 | height
11 | (domino/transact session :height height)
12 | weight
13 | (domino/transact session :weight weight)
14 | :else (disp.home/form
15 | (domino/select session :height)
16 | (domino/select session :weight)
17 | (domino/select session :bmi))))
18 |
19 | (defn ui-routes [{:keys [query-fn]}]
20 | (simpleui/make-routes
21 | ""
22 | [query-fn]
23 | (fn [req]
24 | (let [req (assoc req :query-fn query-fn)
25 | session (or (not-empty (:session req)) (domino/initial-session req))
26 | req (assoc req :session session)]
27 | (-> req bmi-form page-htmx (assoc :session session))))))
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Kit framework team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dominoui/assets/src/ui.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.routes.ui
2 | (:require
3 | [<>.web.middleware.exception :as exception]
4 | [<>.web.middleware.formats :as formats]
5 | [<>.web.views.home :as home]
6 | [<>.web.ws :as ws]
7 | [integrant.core :as ig]
8 | [reitit.ring.middleware.muuntaja :as muuntaja]
9 | [reitit.ring.middleware.parameters :as parameters]))
10 |
11 | (defn route-data [opts]
12 | (merge
13 | opts
14 | {:muuntaja formats/instance
15 | :middleware
16 | [;; Default middleware for ui
17 | ;; query-params & form-params
18 | parameters/parameters-middleware
19 | ;; encoding response body
20 | muuntaja/format-response-middleware
21 | ;; exception handling
22 | exception/wrap-exception]}))
23 |
24 | (derive :reitit.routes/ui :reitit/routes)
25 |
26 | (defmethod ig/init-key :reitit.routes/ui
27 | [_ {:keys [base-path]
28 | :or {base-path ""}
29 | :as opts}]
30 | [base-path (route-data opts)
31 | (conj (home/ui-routes opts)
32 | ["/ws"
33 | (fn [_req]
34 | {:undertow/websocket
35 | {:on-open (fn [{:keys [channel]}] (ws/add-channel channel))
36 | :on-close-message (fn [{:keys [channel]}] (ws/remove-channel channel))}})])])
37 |
--------------------------------------------------------------------------------
/dominoui/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets
5 | [["assets/src/core.clj" "src/clj/<>/web/middleware/core.clj" :force]
6 | ["assets/src/ui.clj" "src/clj/<>/web/routes/ui.clj"]
7 | ["assets/src/htmx.clj" "src/clj/<>/web/htmx.clj"]
8 | ["assets/src/domino.clj" "src/clj/<>/web/domino.clj"]
9 | ["assets/src/ws.clj" "src/clj/<>/web/ws.clj"]
10 | ["assets/src/home.clj" "src/clj/<>/web/views/home.clj"]
11 | ["assets/src/disp/home.clj" "src/clj/<>/web/views/disp/home.clj"]]
12 | :injections
13 | [{:type :edn
14 | :path "resources/system.edn"
15 | :target []
16 | :action :merge
17 | :value {:reitit.routes/ui
18 | {:base-path ""
19 | :env #ig/ref :system/env}}}
20 | {:type :edn
21 | :path "deps.edn"
22 | :target [:deps]
23 | :action :merge
24 | :value {simpleui/simpleui {:git/url "https://github.com/whamtet/simpleui"
25 | :git/sha "a107d7a8a69755dae6cd9954992614a86504c257"}
26 | domino/core {:mvn/version "0.4.0-alpha.3"}}}
27 | {:type :clj
28 | :path "src/clj/<>/core.clj"
29 | :action :append-requires
30 | :value ["[<>.web.routes.ui]"]}]}}}
31 |
--------------------------------------------------------------------------------
/dominoui/assets/src/domino.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.domino
2 | (:require
3 | [domino.core :as domino]
4 | [simpleui.response :as response]
5 | [<>.web.ws :as ws]
6 | [<>.web.views.disp.home :as disp.home]))
7 |
8 | ;; adapted from domino README.md
9 |
10 | (def schema
11 | {:model [[:demographics
12 | [:height {:id :height}]
13 | [:weight {:id :weight}]]
14 | [:vitals
15 | [:bmi {:id :bmi}]]]
16 | :events [{:inputs [:height :weight]
17 | :outputs [:bmi]
18 | :handler (fn [{{:keys [height weight]} :inputs
19 | {:keys [bmi]} :outputs}]
20 | {:bmi (if (and height weight)
21 | (/ weight (* height height))
22 | bmi)})}]
23 | :effects [{:inputs [:bmi]
24 | :handler (fn [{{:keys [bmi]} :inputs}]
25 | (ws/broadcast
26 | (disp.home/bmi-label bmi)))}]})
27 |
28 | (def initial-session (domino/initialize schema {:demographics {:height 1.6 :weight 60.0}}))
29 |
30 | (defn transact [session id v]
31 | (assoc response/no-content
32 | :session (domino/transact session [[[id] v]])))
33 |
34 | (defn select [session id]
35 | (domino/select session id))
36 |
--------------------------------------------------------------------------------
/html/assets/src/pages.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.routes.pages
2 | (:require
3 | [<>.web.middleware.exception :as exception]
4 | [<>.web.pages.layout :as layout]
5 | [integrant.core :as ig]
6 | [reitit.ring.middleware.muuntaja :as muuntaja]
7 | [reitit.ring.middleware.parameters :as parameters]
8 | [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]))
9 |
10 | (defn wrap-page-defaults []
11 | (let [error-page (layout/error-page
12 | {:status 403
13 | :title "Invalid anti-forgery token"})]
14 | #(wrap-anti-forgery % {:error-response error-page})))
15 |
16 | (defn home [request]
17 | (layout/render request "home.html"))
18 |
19 | ;; Routes
20 | (defn page-routes [_opts]
21 | [["/" {:get home}]])
22 |
23 | (def route-data
24 | {:middleware
25 | [;; Default middleware for pages
26 | (wrap-page-defaults)
27 | ;; query-params & form-params
28 | parameters/parameters-middleware
29 | ;; encoding response body
30 | muuntaja/format-response-middleware
31 | ;; exception handling
32 | exception/wrap-exception]})
33 |
34 | (derive :reitit.routes/pages :reitit/routes)
35 |
36 | (defmethod ig/init-key :reitit.routes/pages
37 | [_ {:keys [base-path]
38 | :or {base-path ""}
39 | :as opts}]
40 | (layout/init-selmer! opts)
41 | (fn [] [base-path route-data (page-routes opts)]))
42 |
43 |
--------------------------------------------------------------------------------
/metrics/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets []
5 | :injections [{:type :edn
6 | :path "deps.edn"
7 | :target [:deps]
8 | :action :merge
9 | :value {io.github.kit-clj/kit-metrics {:mvn/version "1.0.2"}}}
10 | {:type :edn
11 | :path "resources/system.edn"
12 | :target []
13 | :action :merge
14 | :value {:metrics/prometheus {}}}
15 | {:type :edn
16 | :path "resources/system.edn"
17 | :target [:handler/ring]
18 | :action :merge
19 | :value {:metrics #ig/ref :metrics/prometheus}}
20 | {:type :edn
21 | :path "resources/system.edn"
22 | :target [:reitit.routes/api]
23 | :action :merge
24 | :value {:metrics #ig/ref :metrics/prometheus}}
25 | {:type :clj
26 | :path "src/clj/<>/core.clj"
27 | :action :append-requires
28 | :value ["[kit.edge.utils.metrics]"]}
29 | {:type :clj
30 | :path "src/clj/<>/web/middleware/core.clj"
31 | :action :append-requires
32 | :value ["[iapetos.collector.ring :as prometheus-ring]"]}
33 | ]}}}
--------------------------------------------------------------------------------
/html/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :actions
4 | {:assets [["assets/home.html" "resources/html/home.html"]
5 | ["assets/error.html" "resources/html/error.html"]
6 | ["assets/css/screen.css" "resources/public/css/screen.css"]
7 | ["assets/img/kit.png" "resources/public/img/kit.png"]
8 | ["assets/src/pages.clj" "src/clj/<>/web/routes/pages.clj"]
9 | ["assets/src/layout.clj" "src/clj/<>/web/pages/layout.clj"]]
10 | :injections [{:type :edn
11 | :path "resources/system.edn"
12 | :target []
13 | :action :merge
14 | :value {:reitit.routes/pages
15 | {:base-path ""
16 | :env #ig/ref :system/env}}}
17 | {:type :edn
18 | :path "deps.edn"
19 | :target [:deps]
20 | :action :merge
21 | :value {selmer/selmer {:mvn/version "1.12.50"}
22 | luminus/ring-ttl-session {:mvn/version "0.3.3"}}}
23 | {:type :clj
24 | :path "src/clj/<>/core.clj"
25 | :action :append-requires
26 | :value ["[<>.web.routes.pages]"]}]}}}
27 |
--------------------------------------------------------------------------------
/html/assets/src/layout.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.pages.layout
2 | (:require
3 | [clojure.java.io]
4 | [selmer.parser :as parser]
5 | [ring.util.http-response :refer [content-type ok]]
6 | [ring.util.anti-forgery :refer [anti-forgery-field]]
7 | [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
8 | [ring.util.response]))
9 |
10 | (def selmer-opts {:custom-resource-path (clojure.java.io/resource "html")})
11 |
12 | (defn init-selmer!
13 | [{:keys [env]}]
14 | ;; disable HTML template caching for live reloading during development
15 | (when (= :dev env) (parser/cache-off!))
16 | (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))))
17 |
18 | (defn render
19 | [request template & [params]]
20 | (-> (parser/render-file template
21 | (assoc params :page template :csrf-token *anti-forgery-token*)
22 | selmer-opts)
23 | (ok)
24 | (content-type "text/html; charset=utf-8")))
25 |
26 | (defn error-page
27 | "error-details should be a map containing the following keys:
28 | :status - error status
29 | :title - error title (optional)
30 | :message - detailed error message (optional)
31 | returns a response map with the error page as the body
32 | and the status specified by the status key"
33 | [error-details]
34 | {:status (:status error-details)
35 | :headers {"Content-Type" "text/html; charset=utf-8"}
36 | :body (parser/render-file "error.html" error-details selmer-opts)})
--------------------------------------------------------------------------------
/sente/assets/ws.cljs:
--------------------------------------------------------------------------------
1 | (ns <>.ws
2 | (:require [taoensso.sente :as sente]))
3 |
4 | (let [connection (sente/make-channel-socket! "/ws" js/csrfToken {:type :auto})]
5 | (def ch-chsk (:ch-recv connection)) ; ChannelSocket's receive channel
6 | (def send-message! (:send-fn connection)))
7 |
8 | (defn state-handler [{:keys [?data]}]
9 | (.log js/console (str "state changed: " ?data)))
10 |
11 | (defn handshake-handler [{:keys [?data]}]
12 | (.log js/console (str "connection established: " ?data)))
13 |
14 | (defn default-event-handler [ev-msg]
15 | (.log js/console (str "Unhandled event: " (:event ev-msg))))
16 |
17 | (defn event-msg-handler [& [{:keys [message state handshake]
18 | :or {state state-handler
19 | handshake handshake-handler}}]]
20 | (fn [ev-msg]
21 | (case (:id ev-msg)
22 | :chsk/handshake (handshake ev-msg)
23 | :chsk/state (state ev-msg)
24 | :chsk/recv (message ev-msg)
25 | (default-event-handler ev-msg))))
26 |
27 | (def router (atom nil))
28 |
29 | (defn stop-router! []
30 | (when-let [stop-f @router] (stop-f)))
31 |
32 | (defn start-router! [message-handler]
33 | (stop-router!)
34 | (reset! router (sente/start-chsk-router!
35 | ch-chsk
36 | (event-msg-handler
37 | {:message message-handler
38 | :state state-handler
39 | :handshake handshake-handler}))))
40 |
--------------------------------------------------------------------------------
/htmx/assets/src/ui.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.routes.ui
2 | (:require
3 | [<>.web.middleware.exception :as exception]
4 | [<>.web.middleware.formats :as formats]
5 | [<>.web.routes.utils :as utils]
6 | [<>.web.htmx :refer [page fragment] :as htmx]
7 | [integrant.core :as ig]
8 | [reitit.ring.middleware.muuntaja :as muuntaja]
9 | [reitit.ring.middleware.parameters :as parameters]))
10 |
11 | (defn home [request]
12 | (page {:lang "en"}
13 | [:head
14 | [:meta {:charset "UTF-8"}]
15 | [:title "Htmx + Kit"]
16 | [:script {:src "https://unpkg.com/htmx.org@2.0.8/dist/htmx.min.js" :defer true}]]
17 | [:body
18 | [:h1 "Welcome to Htmx + Kit module"]
19 | [:button {:hx-post "/clicked" :hx-swap "outerHTML"} "Click me!"]]))
20 |
21 | (defn clicked [request]
22 | (fragment
23 | [:div "Congratulations! You just clicked the button!"]))
24 |
25 | ;; Routes
26 | (defn ui-routes [_opts]
27 | [["/" {:get home}]
28 | ["/clicked" {:post clicked}]])
29 |
30 | (def route-data
31 | {:muuntaja formats/instance
32 | :middleware
33 | [;; Default middleware for ui
34 | ;; query-params & form-params
35 | parameters/parameters-middleware
36 | ;; encoding response body
37 | muuntaja/format-response-middleware
38 | ;; exception handling
39 | exception/wrap-exception]})
40 |
41 | (derive :reitit.routes/ui :reitit/routes)
42 |
43 | (defmethod ig/init-key :reitit.routes/ui
44 | [_ {:keys [base-path]
45 | :or {base-path ""}
46 | :as opts}]
47 | (fn [] [base-path route-data (ui-routes opts)]))
48 |
--------------------------------------------------------------------------------
/cljs/assets/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [clojure.tools.build.api :as b]
3 | [clojure.string :as string]
4 | [clojure.java.shell :refer [sh]]
5 | [deps-deploy.deps-deploy :as deploy]))
6 |
7 | (def lib 'com.example/test)
8 | (def main-cls (string/join "." (filter some? [(namespace lib) (name lib) "core"])))
9 | (def version (format "0.0.1-SNAPSHOT"))
10 | (def target-dir "target")
11 | (def class-dir (str target-dir "/" "classes"))
12 | (def uber-file (format "%s/%s-standalone.jar" target-dir (name lib)))
13 | (def basis (b/create-basis {:project "deps.edn"}))
14 |
15 | (defn clean
16 | "Delete the build target directory"
17 | [_]
18 | (println (str "Cleaning " target-dir))
19 | (b/delete {:path target-dir}))
20 |
21 | (defn prep [_]
22 | (b/write-pom {:class-dir class-dir
23 | :lib lib
24 | :version version
25 | :basis basis
26 | :src-dirs ["src/clj"]})
27 | (b/copy-dir {:src-dirs ["src" "resources" "env/prod"]
28 | :target-dir class-dir}))
29 |
30 | (defn build-cljs [_]
31 | (println "npx shadow-cljs release app...")
32 | (let [{:keys [exit] :as s} (sh "npx" "shadow-cljs" "release" "app")]
33 | (when-not (zero? exit)
34 | (throw (ex-info "could not compile cljs" s)))))
35 |
36 | (defn uber [_]
37 | (b/compile-clj {:basis basis
38 | :src-dirs ["src/clj" "env/prod/clj"]
39 | :class-dir class-dir})
40 | (build-cljs nil)
41 | (println "Making uberjar...")
42 | (b/uber {:class-dir class-dir
43 | :uber-file uber-file
44 | :main main-cls
45 | :basis basis}))
46 |
47 | (defn all [_]
48 | (do (clean nil) (prep nil) (uber nil)))
49 |
--------------------------------------------------------------------------------
/modules.edn:
--------------------------------------------------------------------------------
1 | {:name "kit-modules"
2 | :modules
3 | {:kit/auth
4 | {:path "auth"
5 | :doc "adds support for auth middleware using Buddy"}
6 | :kit/devcontainer
7 | {:path "devcontainer"
8 | :doc "adds support for devcontainer. Available profiles [ :default :compose ]. Default profile uses a single docker file."}
9 | :kit/html
10 | {:path "html"
11 | :doc "adds support for HTML templating using Selmer"}
12 | :kit/htmx
13 | {:path "htmx"
14 | :doc "adds support for HTMX using hiccup"}
15 | :kit/simpleui
16 | {:path "simpleui"
17 | :doc "adds support for HTMX using SimpleUI (previously called ctmx)"}
18 | :kit/dominoui
19 | {:path "dominoui"
20 | :doc "adds support for HTMX using SimpleUI and Domino"}
21 | :kit/dominoui-postgres
22 | {:path "dominoui-postgres"
23 | :doc "adds support for HTMX using SimpleUI and Domino. Includes sample postgres"}
24 | :kit/metrics
25 | {:path "metrics"
26 | :doc "adds support for metrics using prometheus through iapetos"}
27 | :kit/sente
28 | {:path "sente"
29 | :doc "adds support for Sente websockets to cljs"}
30 | :kit/sql
31 | {:path "sql"
32 | :doc "adds support for SQL. Available profiles [ :postgres :sqlite ]. Default profile :sqlite"}
33 | :kit/cljs
34 | {:path "cljs"
35 | :doc "adds support for cljs using shadow-cljs"}
36 | :kit/nrepl
37 | {:path "nrepl"
38 | :doc "adds support for nREPL"}
39 | :kit/tailwind
40 | {:path "tailwind"
41 | :doc "adds support for tailwindcss"}
42 | :kit/hato
43 | {:path "hato"
44 | :doc "adds support for kit-Hato HTTP client"}
45 | :kit/codox
46 | {:path "codox"
47 | :doc "adds support for codox"}
48 | :kit/datomic
49 | {:path "datomic"
50 | :doc "adds support for Datomic"}}}
51 |
--------------------------------------------------------------------------------
/dominoui-postgres/assets/src/domino.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.domino
2 | (:require
3 | [domino.core :as domino]
4 | [simpleui.response :as response]
5 | [<>.web.controllers.user :as user]
6 | [<>.web.ws :as ws]
7 | [<>.web.views.disp.home :as disp.home]))
8 |
9 | ;; adapted from domino README.md
10 |
11 | (defn- schema [{:keys [query-fn]}]
12 | {:model [[:demographics
13 | [:height {:id :height}]
14 | [:weight {:id :weight}]]
15 | [:vitals
16 | [:bmi {:id :bmi}]]]
17 | :events [{:inputs [:height :weight]
18 | :outputs [:bmi]
19 | :handler (fn [{{:keys [height weight]} :inputs
20 | {:keys [bmi]} :outputs}]
21 | {:bmi (if (and height weight)
22 | (/ weight (* height height))
23 | bmi)})}]
24 | :effects [{:inputs [:bmi]
25 | :handler (fn [{{:keys [bmi]} :inputs}]
26 | (ws/broadcast
27 | (disp.home/bmi-label bmi)))}
28 | {:inputs [:weight]
29 | :handler (fn [{{:keys [weight]} :inputs}]
30 | (user/set-weight query-fn weight))}
31 | {:inputs [:height]
32 | :handler (fn [{{:keys [height]} :inputs}]
33 | (user/set-height query-fn height))}]})
34 |
35 | (defn initial-session [req]
36 | (let [{:keys [height weight]} (user/get-user req)]
37 | (domino/initialize (schema req) {:demographics {:height (or height 1.6)
38 | :weight (or weight 60.0)}})))
39 |
40 | (defn transact [session id v]
41 | (assoc response/no-content
42 | :session (domino/transact session [[[id] v]])))
43 |
44 | (defn select [session id]
45 | (domino/select session id))
46 |
--------------------------------------------------------------------------------
/sente/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:require-restart? true
3 | :requires [:kit/cljs]
4 | :actions
5 | {:assets [["assets/ws.clj" "src/clj/<>/web/routes/ws.clj"]
6 | ["assets/ws.cljs" "src/cljs/<>/ws.cljs"]]
7 | :injections [{:type :edn
8 | :path "deps.edn"
9 | :target [:deps]
10 | :action :merge
11 | :value {com.taoensso/sente {:mvn/version "1.17.0"}}}
12 | {:type :edn
13 | :path "shadow-cljs.edn"
14 | :target [:dependencies]
15 | :action :append
16 | :value [com.taoensso/sente "1.17.0"]}
17 | {:type :html
18 | :path "resources/html/home.html"
19 | :action :append
20 | :target [:head]
21 | :value [:script {:type "text/javascript"} "var csrfToken = '{{csrf-token}}';"]}
22 | {:type :edn
23 | :path "resources/system.edn"
24 | :target []
25 | :action :merge
26 | :value {:sente/connection {}
27 |
28 | :sente/router
29 | {:connection #ig/ref :sente/connection}
30 |
31 | :reitit.routes/ws
32 | {:base-path "/ws"
33 | :env #ig/ref :system/env
34 | :connection #ig/ref :sente/connection}}}
35 | {:type :clj
36 | :path "src/clj/<>/core.clj"
37 | :action :append-requires
38 | :value ["[<>.web.routes.ws]"]}
39 | ;; {:type :clj
40 | ;; :path "src/cljs/<>/core.cljs"
41 | ;; :action :append-requires
42 | ;; :value ["[<>.ws :as ws]"]}
43 | ]}}}
44 |
--------------------------------------------------------------------------------
/sente/assets/ws.clj:
--------------------------------------------------------------------------------
1 | (ns <>.web.routes.ws
2 | (:require
3 | [reitit.ring.middleware.parameters :as parameters]
4 | [taoensso.sente :as sente]
5 | [taoensso.sente.server-adapters.undertow :refer [get-sch-adapter]]
6 | [integrant.core :as ig]))
7 |
8 | (defn client-id [ring-req]
9 | (get-in ring-req [:params :client-id]))
10 |
11 | (defmulti on-message :id)
12 |
13 | (defmethod on-message :default
14 | [{:keys [id client-id ?data] :as message}]
15 | (println "on-message: id: " id " cilient-id: " client-id " ?data: " ?data))
16 |
17 | (defmethod on-message :guestbook/echo
18 | [{:keys [id client-id ?data send-fn] :as message}]
19 | (let [response "Hello from the server"]
20 | (send-fn client-id [id response])))
21 |
22 | (defmethod on-message :guestbook/broadcast
23 | [{:keys [id client-id ?data send-fn connected-uids] :as message}]
24 | (let [response (str "Hello to everyone from the client " client-id)]
25 | (doseq [uid (:any @connected-uids)]
26 | (send-fn uid [id response]))))
27 |
28 | (defmethod ig/init-key :sente/connection
29 | [_ opts]
30 | (sente/make-channel-socket!
31 | (get-sch-adapter)
32 | {:packer :edn
33 | ;; :csrf-token-fn nil
34 | :user-id-fn client-id}))
35 |
36 |
37 | (defn handle-message! [msg]
38 | ;; TODO - error handling
39 | (on-message msg))
40 |
41 | (defmethod ig/init-key :sente/router
42 | [_ {:keys [connection] :as opts}]
43 | (sente/start-chsk-router! (:ch-recv connection) #'handle-message!))
44 |
45 | (defmethod ig/halt-key! :sente/router
46 | [_ stop-fn]
47 | (when stop-fn (stop-fn)))
48 |
49 | (defn route-data
50 | [opts]
51 | (merge
52 | opts
53 | {:middleware
54 | [ring.middleware.keyword-params/wrap-keyword-params
55 | ring.middleware.params/wrap-params]}))
56 |
57 | (defn ws-routes [{:keys [connection] :as opts}]
58 | [["" {:get (:ajax-get-or-ws-handshake-fn connection)
59 | :post (:ajax-post-fn connection)}]])
60 |
61 | (derive :reitit.routes/ws :reitit/routes)
62 |
63 | (defmethod ig/init-key :reitit.routes/ws
64 | [_ {:keys [base-path]
65 | :or {base-path ""}
66 | :as opts}]
67 | [base-path (route-data opts) (ws-routes opts)])
68 |
--------------------------------------------------------------------------------
/cljs/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:feature-requires [:base :reagent]}
3 |
4 | :base
5 | {:require-restart? true
6 | :actions
7 | {:assets [["assets/shadow-cljs.edn" "shadow-cljs.edn"]]
8 | :injections [{:type :html
9 | :path "resources/html/home.html"
10 | :action :append
11 | :target [:body]
12 | :value [:div {:id "app"}]}
13 | {:type :html
14 | :path "resources/html/home.html"
15 | :action :append
16 | :target [:body]
17 | :value [:script {:src "/js/app.js"}]}
18 | {:type :edn
19 | :path "deps.edn"
20 | :target [:paths]
21 | :action :append
22 | :value "src/cljs"}
23 | {:type :edn
24 | :path "deps.edn"
25 | :target [:aliases :dev :extra-paths]
26 | :action :append
27 | :value "target/classes/cljsbuild"}
28 | {:type :edn
29 | :path "deps.edn"
30 | :target [:aliases :build :deps]
31 | :action :merge
32 | :value {babashka/fs {:mvn/version "0.1.11"}
33 | babashka/process {:mvn/version "0.3.11"}}}
34 | {:type :clj
35 | :path "build.clj"
36 | :action :append-requires
37 | :value ["[babashka.fs :refer [copy-tree]]"
38 | "[babashka.process :refer [shell]]"]}
39 | {:type :clj
40 | :path "build.clj"
41 | :action :append-build-task
42 | :value (defn build-cljs []
43 | (println "npx shadow-cljs release app...")
44 | (let [{:keys [exit]
45 | :as s} (shell "npx shadow-cljs release app")]
46 | (when-not (zero? exit)
47 | (throw (ex-info "could not compile cljs" s)))
48 | (copy-tree "target/classes/cljsbuild/public" "target/classes/public")))}
49 | {:type :clj
50 | :path "build.clj"
51 | :action :append-build-task-call
52 | :value (build-cljs)}]}}
53 | :reagent
54 | {:requires [:kit/html]
55 | :feature-requires [:base]
56 |
57 | :actions
58 | {:assets [["assets/src/core.cljs" "src/cljs/<>/core.cljs"]
59 | ["assets/package.json" "package.json"]]
60 | :injections [{:type :edn
61 | :path "shadow-cljs.edn"
62 | :target [:dependencies]
63 | :action :append
64 | :value [reagent "1.1.0"]}]}}
65 |
66 | :uix
67 | {:requires [:kit/html]
68 | :feature-requires [:base]
69 | :actions
70 | {:assets [["assets/src/core.uix.cljs" "src/cljs/<>/core.cljs"]
71 | ["assets/package.uix.json" "package.json"]]
72 | :injections [{:type :edn
73 | :path "shadow-cljs.edn"
74 | :target [:dependencies]
75 | :action :append
76 | :value [com.pitch/uix.core "1.1.0"]}
77 | {:type :edn
78 | :path "shadow-cljs.edn"
79 | :target [:dependencies]
80 | :action :append
81 | :value [com.pitch/uix.dom "1.1.0"]}]}}}
82 |
--------------------------------------------------------------------------------
/sql/config.edn:
--------------------------------------------------------------------------------
1 | {:default
2 | {:feature-requires [:base :sqlite]}
3 |
4 | :base
5 | {:require-restart? true
6 | :actions
7 | {:assets [["assets/queries.sql" "resources/sql/queries.sql"]
8 | "resources/migrations"]
9 | :injections [{:type :edn
10 | :path "resources/system.edn"
11 | :target []
12 | :action :merge
13 | :value {:db.sql/query-fn {:conn #ig/ref :db.sql/connection
14 | :options {}
15 | :filename "sql/queries.sql"}
16 | :db.sql/migrations {:store :database
17 | :db {:datasource #ig/ref :db.sql/connection}
18 | :migrate-on-init? true}}}
19 | {:type :edn
20 | :path "resources/system.edn"
21 | :target [:reitit.routes/api]
22 | :action :merge
23 | :value {:query-fn #ig/ref :db.sql/query-fn}}
24 | {:type :edn
25 | :path "deps.edn"
26 | :target [:deps]
27 | :action :merge
28 | :value {io.github.kit-clj/kit-sql-conman {:mvn/version "1.10.4"}
29 | io.github.kit-clj/kit-sql-migratus {:mvn/version "1.0.4"}}}
30 | {:type :clj
31 | :path "src/clj/<>/core.clj"
32 | :action :append-requires
33 | :value ["[kit.edge.db.sql.conman]"
34 | "[kit.edge.db.sql.migratus]"]}]}
35 | }
36 |
37 | :sqlite
38 | {:feature-requires [:base]
39 | :actions
40 | {:injections [{:type :edn
41 | :path "resources/system.edn"
42 | :target []
43 | :action :merge
44 | :value {:db.sql/connection #profile
45 | {:dev {:jdbc-url "jdbc:sqlite:<>_dev.db"}
46 | :test {:jdbc-url "jdbc:sqlite:<>_test.db"}
47 | :prod {:jdbc-url #env JDBC_URL}}}}
48 | {:type :edn
49 | :path "deps.edn"
50 | :target [:deps]
51 | :action :merge
52 | :value {org.xerial/sqlite-jdbc {:mvn/version "3.46.0.0"}}}]}}
53 |
54 | :postgres
55 | {:feature-requires [:base]
56 | :actions
57 | {:injections [{:type :clj
58 | :path "src/clj/<>/core.clj"
59 | :action :append-requires
60 | :value ["[kit.edge.db.postgres]"]}
61 | {:type :edn
62 | :path "resources/system.edn"
63 | :target []
64 | :action :merge
65 | :value {:db.sql/connection #profile
66 | {:dev {:jdbc-url "jdbc:postgresql://localhost:5432/<>_dev?user=<>&password=<>"}
67 | :test {:jdbc-url "jdbc:postgresql://localhost:5432/<>_test?user=<>&password=<>"}
68 | :prod {:jdbc-url #env JDBC_URL}}}}
69 | {:type :edn
70 | :path "deps.edn"
71 | :target [:deps]
72 | :action :merge
73 | :value {org.postgresql/postgresql {:mvn/version "42.3.4"}
74 | io.github.kit-clj/kit-postgres {:mvn/version "1.0.7"}}}]}}}
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Kit Modules
2 |
3 | Kit modules are templates that can be applied to an existing project using the [kit-generator](https://github.com/kit-clj/kit/tree/master/libs/kit-generator). In that, they are different from profiles, which you can apply only when creating a new project.
4 |
5 | ### Using Modules
6 |
7 | Kit embraces the REPL and the generator library is aliased in the `user` namespace as `kit`. Let's see how we can us it to install HTML module in the project. First, we need to sync our module repositories. This is done by running the following command in the REPL:
8 |
9 | ```clojure
10 | user=> (kit/sync-modules)
11 | :done
12 | user=>
13 | ```
14 |
15 | Once the modules are synchronized, we can list the available modules by running `kit/list-modules`:
16 |
17 | ```clojure
18 | user=> (kit/list-modules)
19 | :kit/sql - adds support for SQL. Available profiles [ :postgres :sqlite ]. Default profile :sqlite
20 | :kit/html - adds support for HTML templating using Selmer
21 | :kit/ctmx - adds support for HTMX using CTMX
22 | :kit/sente - adds support for Sente websockets to cljs
23 | :kit/cljs - adds support for cljs using shadow-cljs
24 | :kit/metrics - adds support for metrics using prometheus through iapetos
25 | :kit/auth - adds support for auth middleware using Buddy
26 | :kit/nrepl - adds support for nREPL
27 | :kit/htmx - adds support for HTMX using hiccup
28 | :kit/hato - adds support for kit-Hato HTTP client
29 | :done
30 | user=>
31 | ```
32 |
33 | We can see that the three modules specified in the official modules repository are now available for use. Let's install the HTML module by running `kit/install-module` function and passing it the keyword specifying the module name:
34 |
35 | ```clojure
36 | user=> (kit/install-module :kit/html)
37 | updating file: resources/system.edn
38 | updating file: deps.edn
39 | updating file: src/clj/kit/guestbook/core.clj
40 | applying
41 | action: :append-requires
42 | value: ["[kit.guestbook.web.routes.pages]"]
43 | :kit/html installed successfully!
44 | restart required!
45 | nil
46 | user=>
47 | ```
48 |
49 | Let's restart the REPL and run `(go)` command again to start the application. We should now be able to navigate to `http://localhost:3000` and see the default HTML page provided by the module.
50 |
51 | Generator aims to be idempotent, and will err on the side of safety in case of conflicts. For example, if we attempt to install `:kit/html` module a second time then we'll see he following output:
52 |
53 | ```clojure
54 | user=> (kit/install-module :kit/html)
55 | :kit/html requires following modules: nil
56 | module :kit/html is already installed!
57 | nil
58 | user=>
59 | ```
60 |
61 | Generator lets us know that the module already exists and there is nothing to be done.
62 |
63 | ### Creating Custom Modules
64 |
65 | Modules are managed using git repositories. You can find the official modules [here](https://github.com/kit-clj/modules). Let's take a brief look at what a module repository looks like.
66 |
67 | A module repository must contain a `modules.edn` file describing the modules that are provided. For example, here are the official modules provided by Kit:
68 |
69 | ```clojure
70 | {:name "kit-modules"
71 | :modules
72 | {:kit/html
73 | {:path "html"
74 | :doc "adds support for HTML templating using Selmer"}
75 | :kit/metrics
76 | {:path "metrics"
77 | :doc "adds support for metrics using prometheus through iapetos"}
78 | :kit/sql
79 | {:path "sql"
80 | :doc "adds support for SQL. Available profiles [ :postgres :sqlite ]. Default profile :sqlite"}
81 | :kit/cljs
82 | {:path "cljs"
83 | :doc "adds support for cljs using shadow-cljs"}
84 | :kit/nrepl
85 | {:path "nrepl"
86 | :doc "adds support for nREPL"}}}
87 | ```
88 |
89 | As you can see above, the official repository contains five modules. Let's take a look at the [`:kit/html`](https://github.com/kit-clj/modules/tree/master/html) module to see how it works. This module contains a `config.edn` file and a folder called `assets`. It has the following configuration:
90 |
91 | ```clojure
92 | {:default
93 | {:require-restart? true
94 | :actions
95 | {:assets [["assets/home.html" "resources/html/home.html"]
96 | ["assets/error.html" "resources/html/error.html"]
97 | ["assets/css/screen.css" "resources/public/css/screen.css"]
98 | ["assets/img/kit.png" "resources/public/img/kit.png"]
99 | ["assets/src/pages.clj" "src/clj/<>/web/routes/pages.clj"]
100 | ["assets/src/layout.clj" "src/clj/<>/web/pages/layout.clj"]]
101 | :injections [{:type :edn
102 | :path "resources/system.edn"
103 | :target []
104 | :action :merge
105 | :value {:reitit.routes/pages
106 | {:base-path ""
107 | :env #ig/ref :system/env}}}
108 | {:type :edn
109 | :path "deps.edn"
110 | :target [:deps]
111 | :action :merge
112 | :value {selmer/selmer {:mvn/version "1.12.49"}
113 | luminus/ring-ttl-session {:mvn/version "0.3.3"}}}
114 | {:type :clj
115 | :path "src/clj/<>/core.clj"
116 | :action :append-requires
117 | :value ["[<>.web.routes.pages]"]}]}}}
118 | ```
119 |
120 | We can see that the module has a `:default` profile. Kit module profiles allow us to provide variations of a module with different configurations. For example, a database module could have different profiles for different types of databases. In case of HTML, we only need a single profile.
121 |
122 | The`:require-restart?` key specifies that the runtime needs to be restarted for changes to take effect. This is necessary for modules that add Maven dependencies necessitating JVM restarts to be loaded.
123 |
124 | Next, the module specifies the actions that will be performed. The first action, called `:assets`, specifies new assets that will be added to the project. These are template files that will be read from the `assets` folder and injected in the project.
125 |
126 | The other configuration action is called `:injections` and specifies code that will be injected into existing files within the project. In order to provide support for rendering HTML templates, the module must update Integrant system configuration by adding a reference for new routes to `system.edn`, add new dependencies to `deps.edn`, and finally require the namespace that contains the routes for the pages in the core namespace of the project. The `:action` values in injections depend on the types of assets being manipulated.
127 |
128 | `:edn` injections
129 |
130 | * `:append` - appends the value at the specified path, the value at the path is assumed to be a collection
131 |
132 | ```clojure
133 | {:type :edn
134 | :path "deps.edn"
135 | :target [:paths]
136 | :action :append
137 | :value "target/classes/cljsbuild"}
138 | ```
139 |
140 | * `:merge` - merges value with the value found at the path, the value at the path is assumed to be a map
141 |
142 | ```clojure
143 | {:type :edn
144 | :path "deps.edn"
145 | :target [:deps] ; use [] to merge with the top level map
146 | :action :merge
147 | :value {selmer/selmer {:mvn/version "1.12.49"}
148 | luminus/ring-ttl-session {:mvn/version "0.3.3"}}}
149 | ```
150 |
151 | `:clj` injections
152 |
153 | * `:append-requires` - appends a require in the specified namespace
154 |
155 | ```clojure
156 | {:type :clj
157 | :path "src/clj/<>/core.clj"
158 | :action :append-requires
159 | :value ["[<>.web.routes.pages]"]}
160 | ```
161 |
162 | * `:append-build-task` - appends a build task in `build.clj`
163 |
164 | ```clojure
165 | {:type :clj
166 | :path "build.clj"
167 | :action :append-build-task
168 | :value (defn build-cljs []
169 | (println "npx shadow-cljs release app...")
170 | (let [{:keys [exit]
171 | :as s} (sh "npx" "shadow-cljs" "release" "app")]
172 | (when-not (zero? exit)
173 | (throw (ex-info "could not compile cljs" s)))))}
174 | ```
175 |
176 | * `:append-build-task-call` - appends a function call to the `uber` function in `build.clj`
177 |
178 | ```clojure
179 | {:type :clj
180 | :path "build.clj"
181 | :action :append-build-task-call
182 | :value (build-cljs)}
183 | ```
184 |
185 | HTML injections
186 |
187 | * `:append` - appends a Hiccup form to the target identified by enlive selectors in the specified HTML resource
188 |
189 | ```clojure
190 | {:type :html
191 | :path "resources/html/home.html"
192 | :action :append
193 | :target [:body]
194 | :value [:div {:id "app"}]}
195 | ```
196 |
--------------------------------------------------------------------------------