diff --git a/.github/workflows/graal-tests.yml b/.github/workflows/graal-tests.yml
index b2ea89b..14495f4 100644
--- a/.github/workflows/graal-tests.yml
+++ b/.github/workflows/graal-tests.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
version: 'latest'
@@ -18,12 +18,12 @@ jobs:
components: 'native-image'
github-token: ${{ secrets.GITHUB_TOKEN }}
- - uses: DeLaGuardo/setup-clojure@10.0
+ - uses: DeLaGuardo/setup-clojure@12.5
with:
lein: latest
bb: latest
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
with:
path: ~/.m2/repository
key: deps-${{ hashFiles('deps.edn') }}
diff --git a/.github/workflows/main-tests.yml b/.github/workflows/main-tests.yml
index 900a96e..faa64d6 100644
--- a/.github/workflows/main-tests.yml
+++ b/.github/workflows/main-tests.yml
@@ -10,17 +10,17 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: ${{ matrix.java }}
- - uses: DeLaGuardo/setup-clojure@10.0
+ - uses: DeLaGuardo/setup-clojure@12.5
with:
lein: latest
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
id: cache-deps
with:
path: ~/.m2/repository
diff --git a/.gitignore b/.gitignore
index 5f96acc..7cc5b29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ pom.xml*
/target/
/checkouts/
/logs/
+/wiki/.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80eec79..ec0e2d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,7 +46,7 @@ This is a minor **feature release**, and should be a non-breaking upgrade.
```
> This is a **feature release**. Should be non-breaking.
-> See [here](https://github.com/ptaoussanis/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
+> See [here](https://github.com/taoensso/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
### Since `1.8.0`
@@ -62,7 +62,7 @@ This is a minor **feature release**, and should be a non-breaking upgrade.
```
> This is a **maintenance release**. _Should_ be non-breaking.
-> See [here](https://github.com/ptaoussanis/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
+> See [here](https://github.com/taoensso/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
### Since `v1.7.2`
@@ -77,7 +77,7 @@ This is a minor **feature release**, and should be a non-breaking upgrade.
```
> This is a **maintenance release**. Changes may be BREAKING for some users, see relevant commits referenced below for details.
-> See [here](https://github.com/ptaoussanis/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
+> See [here](https://github.com/taoensso/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
### Changes since `v1.6.0`
@@ -94,7 +94,7 @@ This is a minor **feature release**, and should be a non-breaking upgrade.
```
> Minor feature release. _Should_ be non-breaking.
-> See [here](https://github.com/ptaoussanis/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
+> See [here](https://github.com/taoensso/encore#recommended-steps-after-any-significant-dependency-update) for a tip re: general recommended steps when updating any Clojure/Script dependencies.
Identical to `1.6.0-RC1`.
diff --git a/README.md b/README.md
index b344307..e651d0a 100644
--- a/README.md
+++ b/README.md
@@ -3,17 +3,17 @@
# Truss
-#### Assertions micro-library for Clojure/Script
+### Assertions micro-library for Clojure/Script
**Truss** is a tiny Clojure/Script library that provides fast and flexible **runtime assertions** with **terrific error messages**. Use it as a complement or alternative to [clojure.spec](https://clojure.org/about/spec), [core.typed](https://github.com/clojure/core.typed), etc.
-
+
> A doubtful friend is worse than a certain enemy. Let a man be one thing or the other, and we then know how to meet him. - Aesop
## Latest release/s
-- `2023-07-31` `1.11.0`: [changes](../../releases/tag/v1.11.0)
+- `2023-07-31` `1.11.0`: [release info](../../releases/tag/v1.11.0)
[![Main tests][Main tests SVG]][Main tests URL]
[![Graal tests][Graal tests SVG]][Graal tests URL]
@@ -30,6 +30,14 @@ See [here][GitHub releases] for earlier releases.
- No commitment or costly buy-in: use it just when+where needed
- Perfect for library authors: no bulky dependencies
+## Video demo
+
+See for intro and usage:
+
+
+
+
+
## Quickstart
1\. Add the [relevant dependency](#latest-releases) to your project:
@@ -42,7 +50,7 @@ deps.edn: com.taoensso/truss {:mvn/version "x-y-z"}
2\. Setup your namespace imports:
```clojure
-(ns my-ns (:require [taoensso.truss :as truss :refer (have have! have?)]))
+(ns my-ns (:require [taoensso.truss :as truss :refer [have have?]]))
```
3\. Truss uses the simple `(predicate arg)` pattern familiar to Clojure users:
@@ -73,27 +81,16 @@ That's everything most users will need to know, but see the [documentation](#doc
## Documentation
-- [Full documentation][GitHub wiki] (detailed usage, etc.)
-- Auto-generated API reference: [Codox][Codox docs], [clj-doc][clj-doc docs]
-
-## Motivation
-
-
-
-
-
-See [here][GitHub wiki] for more.
+- [Wiki][GitHub wiki] (getting started, usage, etc.)
+- API reference: [Codox][Codox docs], [clj-doc][clj-doc docs]
## Funding
-You can [help support continued work][funding] on this project, thank you!! ๐
-
-Copyright © 2015-2023 [Peter Taoussanis][].
-Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
+You can [help support][sponsor] continued work on this project, thank you!! ๐
## License
-Copyright © 2014-2023 [Peter Taoussanis][].
+Copyright © 2014-2024 [Peter Taoussanis][].
Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
@@ -103,7 +100,7 @@ Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
[GitHub wiki]: ../../wiki
[Peter Taoussanis]: https://www.taoensso.com
-[funding]: https://www.taoensso.com/clojure/backers
+[sponsor]: https://www.taoensso.com/sponsor
diff --git a/bb/graal_tests.clj b/bb/graal_tests.clj
index b8c00d6..3397ebe 100755
--- a/bb/graal_tests.clj
+++ b/bb/graal_tests.clj
@@ -28,7 +28,9 @@
(let [graalvm-home (System/getenv "GRAALVM_HOME")
bin-dir (str (fs/file graalvm-home "bin"))]
(shell (executable bin-dir "gu") "install" "native-image")
- (shell (executable bin-dir "native-image") "-jar" "target/graal-tests.jar" "--no-fallback" "graal_tests")))
+ (shell (executable bin-dir "native-image")
+ "--features=clj_easy.graal_build_time.InitClojureClasses"
+ "--no-fallback" "-jar" "target/graal-tests.jar" "graal_tests")))
(defn run-tests []
(let [{:keys [out]} (shell {:out :string} (executable "." "graal_tests"))]
diff --git a/project.clj b/project.clj
index 2b0d84d..70b5406 100644
--- a/project.clj
+++ b/project.clj
@@ -7,8 +7,8 @@
{:name "Eclipse Public License - v 1.0"
:url "https://www.eclipse.org/legal/epl-v10.html"}
- :dependencies
- []
+ :test-paths ["test" #_"src"]
+ :dependencies []
:profiles
{;; :default [:base :system :user :provided :dev]
@@ -17,8 +17,18 @@
:c1.11 {:dependencies [[org.clojure/clojure "1.11.1"]]}
:c1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]}
:c1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}
- :test
- {:jvm-opts ["-Dtaoensso.elide-deprecated=true"]
+
+ :graal-tests
+ {:source-paths ["test"]
+ :main taoensso.graal-tests
+ :aot [taoensso.graal-tests]
+ :uberjar-name "graal-tests.jar"
+ :dependencies
+ [[org.clojure/clojure "1.11.1"]
+ [com.github.clj-easy/graal-build-time "1.0.5"]]}
+
+ :dev
+ {:jvm-opts ["-server" "-Dtaoensso.elide-deprecated=true"]
:global-vars
{*warn-on-reflection* true
*assert* true
@@ -27,29 +37,17 @@
:dependencies
[[org.clojure/test.check "1.1.1"]
[com.taoensso/encore "3.77.0"
- :exclusions [com.taoensso/truss]]]}
+ :exclusions [com.taoensso/truss]]]
- :graal-tests
- {:dependencies [[org.clojure/clojure "1.11.1"]
- [com.github.clj-easy/graal-build-time "0.1.4"]]
- :main taoensso.graal-tests
- :aot [taoensso.graal-tests]
- :uberjar-name "graal-tests.jar"}
+ :plugins
+ [[lein-pprint "1.3.2"]
+ [lein-ancient "0.7.0"]
+ [lein-cljsbuild "1.1.8"]
+ [com.taoensso.forks/lein-codox "0.10.10"]]
- :dev
- [:c1.11 :test
- {:jvm-opts ["-server"]
- :plugins
- [[lein-pprint "1.3.2"]
- [lein-ancient "0.7.0"]
- [lein-cljsbuild "1.1.8"]
- [com.taoensso.forks/lein-codox "0.10.10"]]
-
- :codox
- {:language #{:clojure :clojurescript}
- :base-language :clojure}}]}
-
- :test-paths ["test" #_"src"]
+ :codox
+ {:language #{:clojure :clojurescript}
+ :base-language :clojure}}}
:cljsbuild
{:test-commands {"node" ["node" "target/test.js"]}
diff --git a/talk.jpg b/talk.jpg
deleted file mode 100644
index c6b98fb..0000000
Binary files a/talk.jpg and /dev/null differ
diff --git a/wiki/.gitignore b/wiki/.gitignore
new file mode 100644
index 0000000..b43bf86
--- /dev/null
+++ b/wiki/.gitignore
@@ -0,0 +1 @@
+README.md
diff --git a/wiki/1-Getting-started.md b/wiki/1-Getting-started.md
new file mode 100644
index 0000000..86db25c
--- /dev/null
+++ b/wiki/1-Getting-started.md
@@ -0,0 +1,329 @@
+# Setup
+
+Add the [relevant dependency](../#latest-releases) to your project:
+
+```clojure
+Leiningen: [com.taoensso/truss "x-y-z"] ; or
+deps.edn: com.taoensso/truss {:mvn/version "x-y-z"}
+```
+
+And setup your namespace imports:
+
+```clojure
+(ns my-ns (:require [taoensso.truss :as truss :refer [have have?]]))
+```
+
+# Basics
+
+The main way to use Truss is with the [`have`](https://taoensso.github.io/truss/taoensso.truss.html#var-have) macro.
+
+You give it a predicate, and an argument that you believe should satisfy the predicate.
+
+For example:
+
+```clojure
+(defn greet
+ "Given a string username, prints a greeting message."
+ [username]
+ (println "hello" (have string? username)))
+```
+
+In this case the predicate is `string?` and argument is `username`:
+
+- If `(string? username)` is truthy: the invariant **succeeds** and `(have ...)` returns the given username.
+- If `(string? username)` is falsey: the invariant **fails** and a detailed **error is thrown** to help you debug.
+
+That's the basic idea.
+
+These `(have )` annotations are standard Clojure forms that both **documents the intention of the code** in a way that **cannot go stale**, and provides a **runtime check** that throws a detailed error message on any unexpected violation.
+
+Everything else documented here is either:
+
+- Advice on how best to use Truss, or
+- Details on features for convenience or advanced situations
+
+## When to use Truss assertions
+
+You use Truss to **formalize assumptions** that you have about your data (e.g. **function arguments**, **intermediate values**, or **current application state** at some point in your execution flow).
+
+So any time you find yourself making **implementation choices based on implicit information** (e.g. the state your application should be in if this code is running) - that's when you might want to reach for Truss instead of a comment or Clojure assertion.
+
+Use Truss assertions like **salt in good cooking**; a little can go a long way.
+
+## `have` variants
+
+While most users will only need to use the base `have` macro, a few variations are provided for convenience:
+
+Macro | On success | On failure | Subject to elision? | Comment
+:--- | :--- | :--- | :--- | :---
+[have](https://taoensso.github.io/truss/taoensso.truss.html#var-have) | Returns given arg/s | Throws | Yes | Most common
+[have!](https://taoensso.github.io/truss/taoensso.truss.html#var-have.21) | Returns given arg/s | Throws | No | As above, without elision
+[have?](https://taoensso.github.io/truss/taoensso.truss.html#var-have.3F) | Returns true | Throws | Yes | Useful in pre/post conditions
+[have!?](https://taoensso.github.io/truss/taoensso.truss.html#var-have.21.3F) | Returns true | Throws | No | As above, without elision
+
+In all cases:
+
+- The basic syntax is identical
+- The behaviour on failure is identical
+
+What varies is the return value, and whether elision is possible.
+
+# Examples
+
+> All examples are from [`/examples/truss_examples.cljc`](../blob/master/examples/truss_examples.cljc)
+
+Truss's sweet spot is often in longer, complex code (difficult to show here). So these examples are mostly examples of **syntax**, not **use case**. In particular, they mostly focus on simple **argument type assertions** since those are the easiest to understand.
+
+In practice, you'll often find more value from assertions about your **application state** or **intermediate `let` values** _within_ a larger piece of code.
+
+## Inline assertions and bindings
+
+A Truss `(have )` form will either throw or return the given argument. This lets you use these forms within other expressions and within `let` bindings, etc.
+
+```clojure
+;; You can add an assertion inline
+(println (have string? "foo"))
+
+;; Or you can add an assertion to your bindings
+(let [s (have string? "foo")]
+ (println s))
+
+;; Anything that fails the predicate will throw an error
+(have string? 42) ; =>
+;; Invariant failed at truss-examples[41,1]: (string? 42)
+;; {:dt #inst "2023-07-31T09:58:07.927-00:00",
+;; :pred clojure.core/string?,
+;; :arg {:form 42, :value 42, :type java.lang.Long},
+;; :env {:elidable? true, :*assert* true},
+;; :loc
+;; {:ns truss-examples,
+;; :line 41,
+;; :column 1,
+;; :file "examples/truss_examples.cljc"}}
+
+;; Truss also automatically traps and handles exceptions
+(have string? (/ 1 0)) ; =>
+;; Invariant failed at truss-examples[54,1]: (string? (/ 1 0))
+;;
+;; Error evaluating arg: Divide by zero
+;; {:dt #inst "2023-07-31T09:59:06.149-00:00",
+;; :pred clojure.core/string?,
+;; :arg
+;; {:form (/ 1 0),
+;; :value truss/undefined-arg,
+;; :type truss/undefined-arg},
+;; :env {:elidable? true, :*assert* true},
+;; :loc
+;; {:ns truss-examples,
+;; :line 54,
+;; :column 1,
+;; :file "examples/truss_examples.cljc"},
+;; :err
+;; #error
+;; {:cause "Divide by zero"
+;; :via
+;; [{:type java.lang.ArithmeticException
+;; :message "Divide by zero"
+;; :at [clojure.lang.Numbers divide "Numbers.java" 190]}]
+;; :trace
+;; [<...>]}}
+```
+
+## Destructured bindings
+
+```clojure
+;; You can assert against multipe args at once
+(let [[x y z] (have string? "foo" "bar" "baz")]
+ (str x y z)) ; => "foobarbaz"
+
+;; This won't compromise error message clarity
+(let [[x y z] (have string? "foo" 42 "baz")]
+ (str x y z)) ; =>
+;; Invariant failed at truss-examples[89,15]: (string? 42)
+;; {:dt #inst "2023-07-31T10:01:00.991-00:00",
+;; :pred clojure.core/string?,
+;; :arg {:form 42, :value 42, :type java.lang.Long},
+;; :env {:elidable? true, :*assert* true},
+;; :loc
+;; {:ns truss-examples,
+;; :line 89,
+;; :column 15,
+;; :file "examples/truss_examples.cljc"}}
+```
+
+## Attaching debug data
+
+You can attach arbitrary debug data to be displayed on violations:
+
+```clojure
+(defn my-handler [ring-req x y]
+ (let [[x y] (have integer? x y :data {:ring-req ring-req})]
+ (* x y)))
+
+(my-handler {:foo :bar} 5 nil) ; =>
+;; Invariant failed at truss-examples[107,15]: (integer? y)
+;; {:dt #inst "2023-07-31T10:02:03.415-00:00",
+;; :pred clojure.core/integer?,
+;; :arg {:form y, :value nil, :type nil},
+;; :env {:elidable? true, :*assert* true},
+;; :loc
+;; {:ns truss-examples,
+;; :line 107,
+;; :column 15,
+;; :file "examples/truss_examples.cljc"},
+;; :data {:dynamic nil, :arg {:ring-req {:foo :bar}}}}
+```
+
+## Attaching dynamic debug data
+
+And you can attach shared debug data at the `binding` level:
+
+```clojure
+(defn wrap-ring-dynamic-assertion-data
+ "Returns Ring handler wrapped so that assertion violation errors in handler
+ will include `(data-fn )` as debug data."
+ [data-fn ring-handler-fn]
+ (fn [ring-req]
+ (truss/with-data (data-fn ring-req)
+ (ring-handler-fn ring-req))))
+
+(defn ring-handler [ring-req]
+ (have? string? 42) ; Will always fail
+ {:status 200 :body "Done"})
+
+(def wrapped-ring-handler
+ (wrap-ring-dynamic-assertion-data
+ ;; Include Ring session with all handler's assertion errors:
+ (fn data-fn [ring-req] {:ring-session (:session ring-req)})
+ ring-handler))
+
+(wrapped-ring-handler
+ {:method :get :uri "/" :session {:user-name "Stu"}}) ; =>
+;; Invariant failed at truss-examples[136,3]: (string? 42)
+;; {:dt #inst "2023-07-31T10:02:41.459-00:00",
+;; :pred clojure.core/string?,
+;; :arg {:form 42, :value 42, :type java.lang.Long},
+;; :env {:elidable? true, :*assert* true},
+;; :loc
+;; {:ns truss-examples,
+;; :line 136,
+;; :column 3,
+;; :file "examples/truss_examples.cljc"},
+;; :data {:dynamic {:ring-session {:user-name "Stu"}}, :arg nil}}
+```
+
+## Assertions within data structures
+
+```clojure
+;;; Compare
+(have vector? [:a :b :c]) ; => [:a :b :c]
+(have keyword? :in [:a :b :c]) ; => [:a :b :c]
+```
+
+## Assertions within :pre/:post conditions
+
+Just make sure to use the `have?` variant which always returns a truthy val on success:
+
+```clojure
+(defn square [n]
+ ;; Note the use of `have?` instead of `have`
+ {:pre [(have? #(or (nil? %) (integer? %)) n)]
+ :post [(have? integer? %)]}
+ (let [n (or n 1)]
+ (* n n)))
+
+(square 5) ; => 25
+(square nil) ; => 1
+```
+
+## Special predicates
+
+Truss offers some shorthands for your convenience. **These are all optional**: the same effect can always be achieved with an equivalent predicate fn:
+
+```clojure
+;; A predicate can be anything
+(have #(and (integer? %) (odd? %) (> % 5)) 7) ; => 7
+
+;; Omit the predicate as a shorthand for #(not (nil? %))
+(have "foo") ; => "foo"
+(have nil) ; => Error
+
+;;; There's a number of other optional shorthands
+
+;; Combine predicates (or)
+(have [:or nil? string?] "foo") ; => "foo"
+
+;; Combine predicates (and)
+(have [:and integer? even? pos?] 6) ; => 6
+
+;; Element of (checks for set containment)
+(have [:el #{:a :b :c :d nil}] :b) ; => :b
+(have [:el #{:a :b :c :d nil}] nil) ; => nil
+(have [:el #{:a :b :c :d nil}] :e) ; => Error
+
+;; Superset
+(have [:set>= #{:a :b}] #{:a :b :c}) ; => #{:a :b :c}
+
+;; Key superset
+(have [:ks>= #{:a :b}] {:a "A" :b nil :c "C"}) ; => {:a "A" :b nil :c "C"}
+
+;; Non-nil keys
+(have [:ks-nnil? #{:a :b}] {:a "A" :b nil :c "C"}) ; => Error
+```
+
+## Complex validators
+
+No need for any special syntax or concepts, just define a function as you'd like:
+
+```clojure
+;; A custom predicate:
+(defn pos-int? [x] (and (integer? x) (pos? x)))
+
+(defn have-person
+ "Returns given arg if it's a valid `person`, otherwise throws an error"
+ [person]
+ (truss/with-data {:person person} ; (Optional) setup some extra debug data
+ (have? map? person)
+ (have? [:ks>= #{:age :name}] person)
+ (have? [:or nil? pos-int?] (:age person)))
+ person ; Return input if nothing's thrown
+ )
+
+(have-person {:name "Steve" :age 33}) ; => {:name "Steve", :age 33}
+(have-person {:name "Alice" :age "33"}) ; => Error
+```
+
+# Motivation
+
+
+
+Clojure is a beautiful language full of smart trade-offs that tends to produce production code that's short, simple, and easy to understand.
+
+But every language necessarily has trade-offs. In the case of Clojure, **dynamic typing** leads to one of the more common challenges that I've observed in the wild: **debugging or refactoring large codebases**.
+
+Specifically:
+
+ * **Undocumented type assumptions** changing (used to be this thing was never nil; now it can be)
+ * Documented **type assumptions going stale** (forgot to update comments)
+ * **Unhelpful error messages** when a type assumption is inevitably violated (it crashed in production? why?)
+
+Thankfully, this list is almost exhaustive; in my experience these few causes often account for **80%+ of real-world incidental difficulty**.
+
+So **Truss** targets these issues with a **practical 80% solution** that emphasizes:
+
+ 1. **Ease of adoption** (incl. partial/precision/gradual adoption)
+ 2. **Ease of use** (non-invasive API, trivial composition, etc.)
+ 3. **Flexibility** (scales well to large, complex systems)
+ 4. **Speed** (blazing fast => can use in production, in speed-critical code)
+ 5. **Simplicity** (lean API, zero dependencies, tiny codebase)
+
+The first is particularly important since the need for assertions in a good Clojure codebase is surprisingly _rare_.
+
+Every codebase has trivial parts and complex parts. Parts that suffer a lot of churn, and parts that haven't changed in years. Mission-critical parts (bank transaction backend), and those that aren't so mission-critical (prototype UI for the marketing department).
+
+Having the freedom to reinforce code only **where and when you judge it worthwhile**:
+
+ 1. Let's you (/ your developers) easily evaluate the lib
+ 2. Makes it more likely that you (/ your developers) will actually _use_ the lib
+ 3. Eliminates upfront buy-in costs
+ 4. Allows you to retain control over long-term cost/benefit trade-offs
diff --git a/wiki/2-FAQ.md b/wiki/2-FAQ.md
new file mode 100644
index 0000000..357c81b
--- /dev/null
+++ b/wiki/2-FAQ.md
@@ -0,0 +1,87 @@
+# How to report/log violations?
+
+By default, Truss just throws an **exception** on any invariant violations.
+
+You can adjust that behaviour with the [`set-error-fn!`](https://taoensso.github.io/truss/taoensso.truss.html#var-set-error-fn.21) and [`with-error-fn`](https://taoensso.github.io/truss/taoensso.truss.html#var-with-error-fn) utils.
+
+Some common usage ideas:
+
+- Use `with-error-fn` to capture violations during unit testing
+- Use `set-error-fn!` to _log_ violations with something like [Timbre](https://www.taoensso.com/timbre)
+
+# Should I annotate my whole API?
+
+**Please don't**! I'd encourage you to think of Truss assertions like **salt in good cooking**; a little can go a long way, and the need for too much salt can be a sign that something's gone wrong in the cooking.
+
+Another useful analogy would be the Clojure STM. Good Clojure code tends to use the STM very rarely. When you want the STM, you _really_ want it - but many new Clojure developers end up surprised at just how rarely they end up wanting it in an idiomatic Clojure codebase.
+
+Do the interns keep getting that argument wrong despite attempts at making the code as clear as possible? By all means, add an assertion.
+
+More than anything, I tend to use Truss assertions as a form of documentation in long/hairy or critical bits of code to remind myself of any unusual input/output contracts/expectations. E.g. for performance reasons, we _need_ this to be a vector; throw if a list comes in since it means that some consumer has a bug.
+
+# What's the performance cost?
+
+Usually insignificant. Truss has been **highly tuned** to minimize both code expansion size[1] and runtime costs.
+
+In many common cases, a Truss expression expands to no more than `(if (pred arg) arg (throw-detailed-assertion-error!))`.
+
+```clojure
+(quick-bench 1e5
+ (if (string? "foo") "foo" (throw (Exception. "Assertion failure")))
+ (have string? "foo"))
+;; => [4.19 4.17] ; ~4.2ms / 100k iterations
+```
+
+> [1] This can be important for ClojureScript codebases
+
+So we're seeing zero overhead against a simple predicate test in this example. In practice this means that predicate costs dominate.
+
+For simple predicates (including `instance?` checks), modern JITs work great; the runtime performance impact is almost always completely insignificant even in tight loops.
+
+In rare cases where the cost does matter (e.g. for an unusually expensive predicate), Truss supports complete elision in production code.
+
+# How to elide Truss checks?
+
+Disable `clojure.core/*assert*` before macro expansion, and Truss forms will noop. They'll pass their arguments through with **zero performance overhead**.
+
+If you use Leiningen, an easy way to do this is to add the following to your `project.clj`:
+
+```clojure
+:global-vars {*assert* false}
+```
+
+# How to prevent elision?
+
+An extra macro is provided (`have!`) which ignores `*assert*` and so can never be elided. This is handy for implementing (and documenting) critical checks like security assertions that you never want disabled.
+
+```clojure
+(defn get-restricted-resource [ring-session]
+ ;; This is an important security check so we'll use `have!` here instead of
+ ;; `have` to make sure the check is never elided (skipped):
+ (have! string? (:auth-token ring-session))
+
+ "return-restricted-resource-content")
+```
+
+# How does Truss compare to alternatives?
+
+There are several good choices when it comes to providing type and/or structural information to Clojure/Script code, including. [clojure.spec](https://clojure.org/about/spec), [core.typed](https://github.com/clojure/core.typed), [@plumatic/schema](https://github.com/plumatic/schema), [@marick/structural-typing](https://github.com/marick/structural-typing), etc.
+
+How these compare is a tough question to answer briefly since these projects may have different objectives, and sometimes offer very different trade-offs.
+
+Some of the variables to consider might include:
+
+- **Cost of getting started** - e.g. is it cheap/easy to cover an initial/small subset of code?
+- **Ease of learning** - e.g. how complex is the syntax/API for newcomers?
+- **Flexibility at scale** - e.g. likelihood of encountering frustrating limitations?
+- **Performance** - e.g. impact on testing/development/production runtimes?
+
+To make a useful comparison, ultimately one might want some kind of `relevant-power รท relevant-cost`, relative to some specific context and objectives.
+
+For my part, I'm really pleased with the balance of particular trade-offs that Truss offers.
+
+As of 2023, Truss continues to be my preferred/default choice for a wide variety of common cases in projects large and small.
+
+The best general recommendation I can make is to try actually experiment with the options that seem appealing to you. Nothing beats hands-on experience for deciding what best fits your particular needs and tastes.
+
+See [here](../wiki#motivation) for some of the specific objectives I had with Truss.
\ No newline at end of file
diff --git a/wiki/Home.md b/wiki/Home.md
new file mode 100644
index 0000000..af3d591
--- /dev/null
+++ b/wiki/Home.md
@@ -0,0 +1,8 @@
+See the menu to the right for content ๐
+
+# Contributions welcome
+
+**PRs very welcome** to help improve this documentation!
+See the [wiki](../tree/master/wiki) folder in the main repo for the relevant files.
+
+\- [Peter Taoussanis](https://www.taoensso.com)
\ No newline at end of file
diff --git a/wiki/README.md b/wiki/README.md
new file mode 100644
index 0000000..e788516
--- /dev/null
+++ b/wiki/README.md
@@ -0,0 +1,5 @@
+# Attention!
+
+This wiki is designed for viewing from [here](../../../wiki)!
+
+Viewing from GitHub's file browser will result in **broken links**.
\ No newline at end of file