# Outskirts Labs Docs
## Contributing Guide
# Contributing Guide
I welcome third party contributions.
The most straightforward way to do so, is to fork the project and submit a PR.
If you want to contribute even something trivial please do not hesitate, but follow the guidelines below.
If the change you’re proposing is substantial, _please open an issue in the issue tracker in order to discuss it first_.
By submitting a pull request, you agree to license the submitted code under
[the EUPL-1.2 License](https://eupl.eu/1.2/en/) (or whatever the project license actually is).
If you do not own the copyright of the code that is being submitted, please clarify the name of the copyright holder and the license under which the copyrighted material can be used so that we can review if it could be incorporated as part of the project.
If you have questions about contributing contact [Casey](https://casey.link/about) directly :)
## Environment Setup
Nearly all OL projects use nix flake devshells. Getting started is easy, and it works on every platform.
1. Install Nix. Follow the one pager at https://zero-to-nix.com/start/install/
2. Clone the project git repository
3. Run `nix develop` (if you use a different shell: `nix develop --command zsh`)
Tada, now you have a working dev environment.
## Common Development Commands
I have more-or-less standardized on a common set of babashka tasks and a common `bb.edn` across all projects.
```shell
bb fmt # run the formatter
bb lint # run the linter
bb test # run the test suite
```
All three of these must come back clean/green before committing and sharing a PR.
## Continuous Integration (CI)
Most projects use continuous integration to check the commits.
The CI jobs are triggered by pushing to the Git repository and are listed on Github Actions.
If a PR of yours fails a check, be proactive to figure out what failed.
## AI/LLM Policy
If you use LLMs for your contribution (issue, comment, PR), it’s now your job to check and clean up its output, just like with any other tool. You have to spend the time doing this.
Using AI to write code is fine.
What’s not fine is submitting agent-generated slop without understanding it.
You must be able to explain what your changes do and how they interact with the rest of the system. If you can’t, your PR will be closed.
In particular:
* If I can tell that a PR is mostly LLM-generated via the usual indicators that violate the conventional style of the codebase, it’ll take significantly more time/effort than usual to review and finish. I’m not interested in such PRs.
* When using an LLM to write an issue, the text usually has a lot of fluff. No one wants to read it. Clean up the text and keep only the relevant stuff. I care more about accuracy and completeness (but not verbosity!).
* When using an LLM to comment on an issue, you still have to verify that the comment makes sense, contributes something useful, and doesn’t repeat itself unnecessarily. (Yes, irony. If I repeat myself enough in this policy, maybe your LLM will understand)
## datahike-sqlite.core
# datahike-sqlite.core
## datahike-sqlite.konserve
# datahike-sqlite.konserve
## default-table
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L22-L22)
---
## sqlite-bulk-insert-batch-size
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L24-L24)
---
## default-sqlite-pragmas
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L25-L32)
---
## with-write-tx
```clojure
(with-write-tx db f)
```
Wrapper around the with-write-tx macro, for use in situations where we cannot use macros directly.
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L34-L38)
---
## init-db
```clojure
(init-db db-spec)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L40-L46)
---
## create-statement
```clojure
(create-statement table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L48-L49)
---
## upsert-statement
```clojure
(upsert-statement table id header meta value)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L51-L55)
---
## select-exists-statement
```clojure
(select-exists-statement table id)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L57-L58)
---
## delete-row-statement
```clojure
(delete-row-statement table store-key)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L60-L61)
---
## select-row-statement
```clojure
(select-row-statement table id)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L63-L64)
---
## select-rows-statement
```clojure
(select-rows-statement table ids)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L66-L69)
---
## select-table-exists-statement
```clojure
(select-table-exists-statement table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L71-L72)
---
## select-all-ids-statement
```clojure
(select-all-ids-statement table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L74-L75)
---
## select-ids-statement
```clojure
(select-ids-statement table ids)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L77-L80)
---
## delete-rows-statement
```clojure
(delete-rows-statement table store-keys)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L82-L85)
---
## update-id-statement
```clojure
(update-id-statement table from to)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L87-L88)
---
## multi-insert-statement
```clojure
(multi-insert-statement table batch-size)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L90-L98)
---
## copy-row-statement
```clojure
(copy-row-statement table to from)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L100-L104)
---
## delete-store-statement
```clojure
(delete-store-statement table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L106-L107)
---
## change-row-id
```clojure
(change-row-id db table from to)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L109-L113)
---
## read-all
```clojure
(read-all db table id)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L115-L122)
---
## ->SQLiteRow
```clojure
(->SQLiteRow db key data cache)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L129-L171)
---
## SQLiteRow
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L129-L171)
---
## map->SQLiteRow
```clojure
(map->SQLiteRow m)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L129-L171)
---
## ->SQLiteTable
```clojure
(->SQLiteTable db-spec db table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L195-L276)
---
## SQLiteTable
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L195-L276)
---
## map->SQLiteTable
```clojure
(map->SQLiteTable m)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L195-L276)
---
## prepare-spec
```clojure
(prepare-spec db-spec opts-table)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L278-L282)
---
## connect-store
```clojure
(connect-store db-spec & {:keys [opts] :as params})
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L284-L301)
---
## release
```clojure
(release store env)
```
Closes the SQLite database connection pools.
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L303-L310)
---
## delete-store
```clojure
(delete-store db-spec & {:keys [table opts]})
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L312-L317)
---
## store-exists?
```clojure
(store-exists? db-spec & {:keys [table opts]})
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/konserve.clj#L319-L329)
## datahike-sqlite.store-config
# datahike-sqlite.store-config
## default-table
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/store_config.clj#L7-L7)
---
## derive-store-id
```clojure
(derive-store-id {:keys [dbname table]})
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/store_config.clj#L9-L13)
---
## store-id
```clojure
(store-id store-config)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/store_config.clj#L15-L18)
---
## ensure-store-id
```clojure
(ensure-store-id store-config)
```
[source,window=_blank](https://github.com/outskirtslabs/datahike-sqlite/blob/main/src/datahike_sqlite/store_config.clj#L20-L24)
## Datahike SQLite Backend
# Datahike SQLite Backend
> SQLite storage backend for Datahike, the open datalog database.



This library provides a backend for
[Datahike](https://github.com/replikativ/datahike) using
[SQLite](https://www.sqlite.org) as the backing store, with direct native
integration through
[sqlite4clj](https://github.com/andersmurphy/sqlite4clj).
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Why sqlite4clj?
Unlike the [datahike-jdbc](https://github.com/replikativ/datahike-jdbc/)
backend, this implementation uses
[sqlite4clj](https://github.com/andersmurphy/sqlite4clj) - a minimalist
FFI binding to SQLite’s C API using Java 22’s Foreign Function Interface
(Project Panama). This approach offers several advantages:
* Bypasses JDBC overhead by interfacing directly with SQLite’s C API
through FFI for direct SQLite access.
* SQLite’s embedded nature doesn’t require thread-backed connection
pools like c3p0/HikariCP, eliminating that complexity.
* Provides better performance through cached prepared statements per
connection and inline caching of column reading functions.
* Eliminates dependencies on sqlite-jdbc, c3p0, and next.jdbc for a
smaller footprint.
* Easier access to SQLite-specific features and pragmas for targeted
optimizations.
* More suitable architecture for SQLite’s single-writer, multiple-reader
model.
## Installation
Include the library in your deps.edn:
```clojure
org.replikativ/datahike {:mvn/version "0.8.1686"} ;; Use latest 0.8.x version
com.outskirtslabs/datahike-sqlite {:git/url "https://github.com/outskirtslabs/datahike-sqlite"
:git/sha "7b6f3f8c3a88974b0ccd22c57086fb94c6ee92f0"}
```
### Dependencies
* The https://clojars.org/org.replikativ/datahike[org.replikativ/datahike
dependency] must be provided by your project and must be 0.8.x or greater
* [sqlite4clj](https://github.com/andersmurphy/sqlite4clj) requires Java
22 or later.
* If you use this library as a git dependency, you will need to prepare
the library with `clj -X:deps prep`.
* You must include
`:jvm-opts ++[++"--enable-native-access=ALL-UNNAMED"++]++` in your
deps.edn alias.
* When creating an executable jar file, you can avoid the need to pass
this argument by adding the manifest attribute
`Enable-Native-Access: ALL-UNNAMED` to your jar.
### Example
```clojure
(require
'[datahike-sqlite.core] ;; required to pull in the multi-method implementations
'[datahike.api :as d])
(def cfg {:store {:backend :sqlite
:dbname "foobar.sqlite"
;; required by modern konserve/datahike
:id #uuid "550e8400-e29b-41d4-a716-446655440000"
;; see sqlite4clj.core/init-db! for the possible options
:sqlite-opts {:pool-size 4}}})
(d/database-exists? cfg)
;; => false
(d/create-database cfg)
(def conn (d/connect cfg))
(d/transact conn [{:db/ident :artifact
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :level
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one}])
(d/transact conn [{:artifact "Mighty Teapot" :level 20}])
(d/q '[:find (pull ?e [*])
:in $ ?artifact
:where [?e :artifact ?artifact]]
@conn "Mighty Teapot")
(d/release conn)
;; this will delete the table in the sqlite file,
;; but will not delete the sqlite file itself
(d/delete-database cfg)
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/datahike-sqlite/next/)
* [API Reference](https://docs.outskirtslabs.com/datahike-sqlite/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datahike-sqlite/issues)
## Configuration
The value for `:store` is a configuration map. To invoke datahike-sqlite
(this library) you must include `:backend :sqlite` in that map.
You must also include `:dbname`, a path to the SQLite file.
Konserve 0.9 and newer Datahike releases require a UUID under `:id`.
Use a stable UUID literal in your store config.
You can optionally include the key `:sqlite-opts` with an options map
which will be passed to
[`sqlite4clj.core/init-db!`](https://github.com/andersmurphy/sqlite4clj).
## Development
### Benchmarking
The SQLite KV benchmark suite lives in
`bench/datahike_sqlite/kv_benchmark.clj`.
Run the full suite with:
```bash
clojure -M:dev:bench -m datahike-sqlite.kv-benchmark
```
Run a smaller smoke benchmark with:
```bash
clojure -M:dev:bench -m datahike-sqlite.kv-benchmark '{:mode :smoke}'
```
Run the suite at a specific entry count with:
```bash
clojure -M:dev:bench -m datahike-sqlite.kv-benchmark 1000
```
Write the benchmark results to a file with:
```bash
clojure -M:dev:bench -m datahike-sqlite.kv-benchmark '{:n 1000 :output "bench/results/sqlite-kv.txt"}'
```
### Testing
```bash
bb test
```
### Formatting
```shell
bb fmt
```
### Linting
```shell
bb lint
```
## License: MIT License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT](https://spdx.org/licenses/MIT.html), like dathike.
## starfederation.datastar.clojure.expressions
# starfederation.datastar.clojure.expressions
## ->expr
```clojure
(->expr & forms)
```
Compiles a clojure form into a datastar expression.
TODO docs
_macro_
[source,window=_blank](https://github.com/outskirtslabs/datastar-expressions/blob/main/src/starfederation/datastar/clojure/expressions.clj#L8-L15)
## datastar-expressions
# datastar-expressions
> Clojure to Datastar expression transpiler



`expressions` is a proof-of-concept for writing https://data-star.dev[🚀
datastar] expressions using Clojure without manual string concatenation.
Instead of:
```clojure
[:button {:data-on-click (str "$person-id" (:id person) " @post('/update-person')")}]`
```
Write this:
```clojure
[:button {:data-on-click (->expr
(set! $person-id ~(:id person))
(@post "/update-person"))}]
```
It is powered by [squint](https://github.com/squint-cljs/squint), thanks
[@borkdude](https://github.com/borkdude).
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Goal & Non-Goals
Since Clojure does not have string interpolation, writing even simple
https://data-star.dev/guide/datastar_expressions[Datastar (d++*++)
expressions] can involve a lot of `str` or `format` gymnastics.
The goal of `expressions` is to add a little bit of syntax sugar when
writing d++*++ expressions so that they can be read and manipulated as
s-expressions.
D++*++ expressions are not exactly javascript, though they are
interpreted by the js runtime. D++*++ expressions also do not have a
grammar or really any formal definition of any kind. Delaney’s official
position is that the simplest and obvious expressions a human would
write should work.
`expressions` follows that by trying to provide coverage for 99% of
simple and obvious expressions.
⚠️ You can totally write expressions that result in broken javascript,
that is not necessarily a bug.
## Install
```clojure
datastar/expressions {:git/url "https://github.com/outskirtslabs/datastar-expressions/"
:git/sha "53efc7093be1ba33b331b4a27884be8925d8bdce"}
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/datastar-expressions/next/)
* [API Reference](https://docs.outskirtslabs.com/datastar-expressions/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datastar-expressions/issues)
## Status
`expressions` is experimental and breaking changes will occur as it is
actively being developed. Please share your feedback so we can squash
bugs and arrive at a stable release.
## REPL Exploration
To see what this is all about, you can clone this repo and play with the
demos:
```
clojure -M:dev ;; (bring your own repl server)
```
Check out [`dev/user.clj`](./dev/user.clj) and
[`dev/demo.clj`](./dev/demo.clj)
## Example Usage
```clojure
(ns user
(:require [starfederation.datastar.clojure.expressions :refer [->expr]]))
;; Samples
(def record {:record-id "1234"})
;; You have to unquote (~) forms you want evaluated
;; Otherwise no quoting is needed!
;; vars and locals are available for evaluation
(let [val 42]
(->expr
(set! $forty-two ~val)))
;; => "$forty-two = 42;"
(let [val (random-uuid)]
(->expr
(set! $forty-two ~(str val))))
;; => "$forty-two = \"745a9225-890f-41a7-9fc4-008770a68e7e\";"
;; kebab case preservation
(->expr
(set! $record-id ~(:record-id record)))
;; => "$record-id = \"1234\";"
;; actually... all case preservation :)
(->expr
(set! $record_id ~(:record-id record)))
;; => "$record_id = \"1234\";"
(->expr
(set! $recordId ~(:record-id record)))
;; => "$recordId = \"1234\";"
;; namespaced signals work of course
(->expr
(set! $person.first-name "alice"))
;; => "$person.first-name = \"alice\";"
;; primitive functions work too (squint adds parens, but its ok)
(let [val 1]
(->expr
(set! $forty-two (+ ~val $forty-one))))
;; => "$forty-two = (1) + ($forty-one);"
;; calling js functions:
(->expr (pokeBear $bear-id))
;; => "pokeBear($bear-id)"
;; actions
(->expr (@get "/poke"))
;; => "@get(\"/poke\")"
(->expr (@patch "/poke"))
;; => "@patch(\"/poke\")"
;; expr with multiple statements are in order like you would expect
(->expr
(set! $bear-id 1234)
(pokeBear $bear-id)
(@post "/bear-poked"))
;; => "$bear-id = 1234;; pokeBear($bear-id); @post(\"/bear-poked\")"
;; You can build dynamic signal names by using the $signal in the first position
(let [field-name "name"]
(->expr
(set! ($bear. ~field-name) "Yogi")
(@post "/bear")))
;; => "$bear.name = \"Yogi\";; @post(\"/bear\")"
;; logical conjunctions and disjunctions
(->expr (and (= $my-signal "bar")
"ret-val"))
;; => "(($my-signal) === (\"bar\")) && (\"ret-val\")"
;; But you should probably use when/if
(->expr (when (= $my-signal "bar")
"ret-val"))
;; => "((($my-signal) === (\"bar\")) ? ((\"ret-val\")) : (null))"
(->expr (if (= $my-signal "bar")
"true-val"
"false-val"))
;; => "((($my-signal) === (\"bar\")) ? (\"true-val\") : (\"false-val\"))"
;; A few other variations
(->expr (&& (or (= evt.key "Enter")
(&& evt.ctrlKey (= evt.key "1")))
(alert "Key Pressed")))
;; => "(((evt.key) === (\"Enter\")) || ((evt.ctrlKey) && ((evt.key) === (\"1\")))) && (alert(\"Key Pressed\"))"
;; This one is interesting, see how it uses the , operator to separate sub-expressions
(->expr (when (= evt.key "Enter")
(evt.preventDefault)
(alert "Key Pressed")))
;; => "(((evt.key) === (\"Enter\")) ? ((evt.preventDefault()), (alert(\"Key Pressed\"))) : (null))"
;; And here is one for data-class
(->expr {"hidden" (&& $fetching-bears
(= $bear-id 1))})
;; => "({ \"hidden\": ($fetching-bears) && (($bear-id) === (1)) })"
;; It also does edn->json conversion, so setting initial signals is possible
(->expr {:my-signal "init-value"})
;; => "({ \"my-signal\": \"init-value\" })"
(->expr
(let [value $my-signal]
(println value)
(and (= $my-signal "bear")
(@post "/foo"))))
;; => "(() => { const value1 = $my-signal; console.log((value1)); return (($my-signal) === (\"bear\")) && (@post(\"/foo\")); })()"
;; JS template strings are supported
;; Since ` is used by the reader, we just wrap the whole thing in quotes
(->expr
(@post ("`/ping/${evt.srcElement.id}`")))
;; => "@post(`/ping/${evt.srcElement.id}`)"
;; Negation
(->expr (not $foo))
;; => "(!($foo))"
(->expr (not (= 1 2)))
;; => "(!((1) === (2)))"
(->expr (not= (+ 1 3) 4))
;; => "((1) + (3)) !== (4)"
(->expr (set! $ui._leftnavOpen (not $ui._leftnavOpen)))
;; => "$ui._leftnavOpen = (!($ui._leftnavOpen))"
;; if
(->expr (set! $ui._leftnavOpen (if $ui._leftnavOpen false true)))
;; => "$ui._leftnavOpen = (($ui._leftnavOpen) ? (false) : (true))"
(->expr (if $ui._leftnavOpen
(set! $ui._leftnavOpen false)
(set! $ui._leftnavOpen true)))
;; => "(($ui._leftnavOpen) ? ($ui._leftnavOpen = false) : ($ui._leftnavOpen = true))"
;; expr/raw is an escape hatch to emit raw JS
;; raw/1 emits its argument as is
(->expr (set! $foo (expr/raw "!$foo")))
;; => "$foo = !$foo"
(let [we-are "/back-in-string-concat-land"]
(->expr
(set! $volume 11)
(expr/raw ~(str "window.location = " we-are))))
;; => "$volume = 11; window.location = /back-in-string-concat-land"
;; raw/0 emits nothing
(->expr (set! $foo (expr/raw)))
;; => "$foo ="
;; bare symbols
(->expr $ui._mainMenuOpen)
;; => "$ui._mainMenuOpen"
;; when-not
(->expr (when-not (= 1 1)
(set! $ui._mainMenuOpen true)))
;; => "(((1) === (1)) ? (null) : ($ui._mainMenuOpen = true))"
;; bare booleans
(->expr (when false
(set! $foo true)))
;; => "((!!(false)) ? (($foo = true)) : (null))"
```
## Known Limitations
```clojure
;; a generated symbol (el-id below) cannot be used in a template string
(->expr (let [el-id evt.srcElement.id]
(when el-id
(@post ("`/ping/${el-id}`")))))
;; => "(() => { const el_id1 = evt.srcElement.id; if (el_id1) { return @post(`/ping/${el-id}`)} else { return alert(\"No id\")}; })()"
;; No condp
(->expr (condp = $ui._mainMenuOpen
true (set! $ui._mainMenuOpen false)
false (set! $ui._mainMenuOpen true)))
;; => "(() => { const pred__288831 = squint_core._EQ_; const expr__288842 = $ui._mainMenuOpen; if (!!(pred__288831(true, expr__288842))) { return $ui._mainMenuOpen = false} else { if (!!(pred__288831(false, expr__288842))) { return $ui._mainMenuOpen = true} else { throw new java.lang.IllegalArgumentException(squint_core.str(\"No matching clause: \", expr__288842))}}; })()"
```
## License: MIT License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT](https://spdx.org/licenses/MIT.html).
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7491
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7491;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7491
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7491;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7491
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7491;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7491
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7491;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7491
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7491
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7491;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.13.0 (2026-04-01)
This is a version bump release:
* Added package versions for
[version 1.0.7556](https://docs.datomic.com/changes/pro.html#1.0.7556)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7556` (latest)
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7556` (latest)
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7556
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7556
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7556
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7556
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7556
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7556
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7556;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7556`
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7556`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
## v0.14.0 (2026-04-29)
This is a version bump release:
* Added package versions for
[version 1.0.7622](https://docs.datomic.com/changes/pro.html#1.0.7622)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7622` (latest)
* `pkgs.datomic-pro_1_0_7556`
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7622` (latest)
* `pkgs.datomic-pro-peer_1_0_7556`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.13.0 (2026-04-01)
This is a version bump release:
* Added package versions for
[version 1.0.7556](https://docs.datomic.com/changes/pro.html#1.0.7556)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7556` (latest)
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7556` (latest)
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7622
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7622;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7622` (latest)
* `pkgs.datomic-pro_1_0_7556`
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7622` (latest)
* `pkgs.datomic-pro-peer_1_0_7556`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## [UNRELEASED]
* The flake is now tracking nixpkgs stable 26.05
## v0.14.0 (2026-04-29)
This is a version bump release:
* Added package versions for
[version 1.0.7622](https://docs.datomic.com/changes/pro.html#1.0.7622)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7622` (latest)
* `pkgs.datomic-pro_1_0_7556`
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7622` (latest)
* `pkgs.datomic-pro-peer_1_0_7556`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.13.0 (2026-04-01)
This is a version bump release:
* Added package versions for
[version 1.0.7556](https://docs.datomic.com/changes/pro.html#1.0.7556)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7556` (latest)
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7556` (latest)
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.12.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7491](https://docs.datomic.com/changes/pro.html#1.0.7491)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7491` (latest)
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7491` (latest)
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.11.0 (2026-02-03)
### Breaking
* NixOS module: The `services.datomic-pro.package` option is now
required. You must explicitly pin your Datomic version. This change
prevents unexpected upgrades that could affect your data.
* Switch from [SemVer](https://semver.org/spec/v2.0.0.html) to
[BreakVer](https://www.taoensso.com/break-versioning). I actually had
meant to use BreakVer all along and thought we were, but didn’t see the
copy paste error until just now (yes this is a violation of SemVer by
not bumping the major version number).
### Added
* Add automatic version bumping. Thanks to @licht1stein for
contributing.
## v0.10.0 (2026-02-03)
This is a version bump release:
* Added package versions for
[version 1.0.7482](https://docs.datomic.com/changes/pro.html#1.0.7482)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7482` (latest)
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7482` (latest)
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.9.0 (2025-12-14)
This is a version bump release:
* Added package versions for
[version 1.0.7469](https://docs.datomic.com/changes/pro.html#1.0.7469)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7469` (latest)
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7469` (latest)
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
### New Contributors
* @licht1stein made their first contribution in
https://github.com/outskirtslabs/datomic-pro-flake/pull/6
## v0.8.0 (2025-09-08)
The project repository has moved from my personal github to my
open-source project org:
* from https://github.com/Ramblurr/datomic-pro-flake
* to https://github.com/outskirtslabs/datomic-pro-flake/
This is also a version bump release:
* Added package versions for
[version 1.0.7394](https://docs.datomic.com/changes/pro.html#1.0.7394)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7394` (latest)
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7394` (latest)
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.7.0 (2025-07-09)
This is a version bump release:
* Added package versions for
[version 1.0.7387](https://docs.datomic.com/changes/pro.html#1.0.7387)
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7387` (latest)
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7387` (latest)
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.6.1 (2025-05-15)
No changes, just ci wrangling.
## v0.6.0 (2025-05-15)
This release brings versioned packages! We recommend you pin your
deployments to specific versions and upgrade intentionally.
### Changed
Package versions:
`pkgs.datomic-pro` will always be the latest release, but the following
specific versions are also available:
* `pkgs.datomic-pro_1_0_7364` (latest)
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7364` (latest)
* `pkgs.datomic-pro-peer_1_0_7277`
## v0.5.0 (2025-05-15)
This release brings a Datomic version bump to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364).
Also notably the container image size is now 433M, down from over 750M,
thanks to Datomic’s efforts to slim down the release jar!
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to
[version 1.0.7364](https://docs.datomic.com/changes/pro.html#1.0.7364)
* docs: Improved SQLite example with rails 8 inspired tuning
## v0.4.0 (2025-03-14)
### Changed
* nix pkg: Updated `datomic-pro` and `datomic-pro-peer` to version
1.0.7277
## v0.3.0 (2024-11-01)
Nothing changed in 0.3.0, I just am struggling with
[flakehub’s](https://flakehub.com/flake/ramblurr/datomic-pro?view=releases)
release process.
## v0.2.0 (2024-11-01)
### Breaking
* nix pkg: `transactor` bin renamed to `datomic-transactor`
* nix pkg: `console` bin renamed to `datomic-console`
* nixos module: removed the default settings that leaned towards dev/h2
storage by default
### Added
* oci image: Added Docker container image with lots of customizable
features
* Includes sqlite, postgresql, and mysql JDBC drivers by default
* Ability to customize the CLASSPATH and LD_LIBRARY_PATH
* `unstable` container image tag that follows the `main` branch
* nix pkg: Added ability to override the build and add extra native libs
or java libs
* nix pkg: Exposed more packages: `datomic-shell`, `datomic-run`,
`datomic-repl`, `datomic-peer-server`
* nixos module: You can now configure: logging, extra classpath entries,
and extra java options.
* nix pkg: Added datomic-pro-peer package which is the datomic peer
library with all of its dependencies
* nix pkg: Added option to build slimmed down JRE for datomic-pro
### Changed
* nix pkg: Updated datomic-pro to version 1.0.7260
* nix pkg: Switched to Nix’s JDK 21 headless package (which is supported
by Datomic)
* oci image: Use the slimmed down JRE and a custom babashka build to
reduce size of the image
### Fixed
* This changelog formatting
## v0.1.0 (2024-06-12)
### Added
* Created this flake with datomic-pro version 1.0.7075
## Docker/OCI Container
# Docker/OCI Container
`datomic-pro-flake` publishes an OCI image that can run Datomic Pro transactor or Datomic Console.
* Image: `ghcr.io/outskirtslabs/datomic-pro`
* Default command: transactor
* Console command: `console`
If you do not want to build with nix, pull a published image:
```shell
docker pull ghcr.io/outskirtslabs/datomic-pro:1.0.7622
```
Package tags are listed at:
https://github.com/orgs/outskirtslabs/packages/container/package/datomic-pro
## Transactor Mode
Transactor mode runs when no command is provided.
* Default port: `4334`
* Required rw volume: `/config`
* Optional rw volume: `/data` (for local/H2 or sqlite-style setups)
* PostgreSQL, MySQL, and SQLite JDBC drivers are included
You can provide `/config/transactor.properties` directly, or configure via env vars.
### Supported Environment Variables
- ❗ IMPORTANT
-
Every supported variable can also be passed with `_FILE` to load the value from a file.
Example: `DATOMIC_STORAGE_ADMIN_PASSWORD_FILE=/run/secrets/admin-password`.
* `DATOMIC_TRANSACTOR_PROPERTIES_PATH` (`/config/transactor.properties`)
* `DATOMIC_ALT_HOST` (`alt-host`)
* `DATOMIC_DATA_DIR` (`data-dir`, default `/data`)
* `DATOMIC_ENCRYPT_CHANNEL` (`encrypt-channel`)
* `DATOMIC_HEARTBEAT_INTERVAL_MSEC` (`heartbeat-interval-msec`)
* `DATOMIC_HOST` (`host`, default `0.0.0.0`)
* `DATOMIC_MEMCACHED` (`memcached`)
* `DATOMIC_MEMCACHED_AUTO_DISCOVERY` (`memcached-auto-discovery`)
* `DATOMIC_MEMCACHED_CONFIG_TIMEOUT_MSEC` (`memcached-config-timeout-msec`)
* `DATOMIC_MEMCACHED_PASSWORD` (`memcached-password`)
* `DATOMIC_MEMCACHED_USERNAME` (`memcached-username`)
* `DATOMIC_MEMORY_INDEX_MAX` (`memory-index-max`, default `256m`)
* `DATOMIC_MEMORY_INDEX_THRESHOLD` (`memory-index-threshold`, default `32m`)
* `DATOMIC_OBJECT_CACHE_MAX` (`object-cache-max`, default `128m`)
* `DATOMIC_PID_FILE` (`pid-file`)
* `DATOMIC_HEALTHCHECK_CONCURRENCY` (`ping-concurrency`)
* `DATOMIC_HEALTHCHECK_HOST` (`ping-host`)
* `DATOMIC_HEALTHCHECK_PORT` (`ping-port`)
* `DATOMIC_PORT` (`port`, default `4334`)
* `DATOMIC_PROTOCOL` (`protocol`, default `dev`)
* `DATOMIC_READ_CONCURRENCY` (`read-concurrency`)
* `DATOMIC_SQL_DRIVER_CLASS` (`sql-driver-class`)
* `DATOMIC_SQL_URL` (`sql-url`)
* `DATOMIC_STORAGE_ACCESS` (`storage-access`, default `remote`)
* `DATOMIC_STORAGE_ADMIN_PASSWORD` (`storage-admin-password`)
* `DATOMIC_STORAGE_DATOMIC_PASSWORD` (`storage-datomic-password`)
* `DATOMIC_VALCACHE_MAX_GB` (`valcache-max-gb`)
* `DATOMIC_VALCACHE_PATH` (`valcache-path`)
* `DATOMIC_WRITE_CONCURRENCY` (`write-concurrency`)
To disable env-to-properties generation and use your own full config file, set:
* `DOCKER_DATOMIC_GENERATE_PROPERTIES_SKIP` to any non-empty value
## Console Mode
Run with `console` as the first argument.
* Default port: `8080`
* `DB_URI` sets the connection URI
* `DB_URI_FILE` loads the URI from a file
## Example Compose
### Datomic Pro With Local Storage
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
volumes:
- ./data:/data
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
command: console
environment:
DB_URI: datomic:dev://datomic-transactor:4334/?password=unsafe
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With SQLite Storage
Prepare the sqlite database first:
```shell
mkdir -p data/ config/
sqlite3 data/datomic-sqlite.db '
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);'
```
Then run compose:
```yaml
---
services:
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
environment:
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:sqlite:/data/datomic-sqlite.db
DATOMIC_SQL_DRIVER_CLASS: org.sqlite.JDBC
DATOMIC_JAVA_OPTS: -Dlogback.configurationFile=/config/logback.xml
DATOMIC_HOST: datomic-transactor
DATOMIC_ALT_HOST: "127.0.0.1"
volumes:
- "./data:/data:z"
- "./config:/config:z"
ports:
- 127.0.0.1:4334:4334
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:unstable
command: console
environment:
DB_URI: "datomic:sql://?jdbc:sqlite:/data/datomic-sqlite.db"
volumes:
- "./data:/data:z"
ports:
- 127.0.0.1:8081:8080
```
### Datomic Pro With PostgreSQL And Memcached
```yaml
---
services:
datomic-memcached:
image: docker.io/memcached:latest
command: memcached -m 1024
ports:
- 127.0.0.1:11211:11211
restart: always
datomic-storage:
image: docker.io/library/postgres:latest
environment:
POSTGRES_PASSWORD: unsafe
command: postgres -c 'max_connections=1024'
volumes:
- ./data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
datomic-storage-migrator:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
PGUSER: postgres
PGPASSWORD: unsafe
volumes:
- "./postgres-migrations:/migrations"
entrypoint: /bin/sh
command: >
-c '(psql -h datomic-storage -lqt | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -f /opt/datomic-pro/bin/sql/postgres-db.sql) &&
(psql -h datomic-storage -d datomic -c "\\dt" | grep -q "datomic_kvs" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-table.sql) &&
(psql -h datomic-storage -d datomic -c "\\du" | cut -d \| -f 1 | grep -qw "datomic" || psql -h datomic-storage -d datomic -f /opt/datomic-pro/bin/sql/postgres-user.sql)'
datomic-transactor:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
environment:
DATOMIC_STORAGE_ADMIN_PASSWORD: unsafe
DATOMIC_STORAGE_DATOMIC_PASSWORD: unsafe
DATOMIC_PROTOCOL: sql
DATOMIC_SQL_URL: jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
DATOMIC_HEALTHCHECK_HOST: 127.0.0.1
DATOMIC_HEALTHCHECK_PORT: 9999
DATOMIC_MEMCACHED: datomic-memcached:11211
ports:
- 127.0.0.1:4334:4334
restart: always
datomic-console:
image: ghcr.io/outskirtslabs/datomic-pro:1.0.7622
command: console
environment:
DB_URI: datomic:sql://?jdbc:postgresql://datomic-storage:5432/datomic?user=datomic&password=datomic
ports:
- 127.0.0.1:8081:8080
```
## Discussion
If something is missing for your deployment style, open an issue:
https://github.com/outskirtslabs/datomic-pro-flake/issues/new
## datomic-pro-flake
# datomic-pro-flake
> Datomic Pro packaged for Nix w/ NixOS modules and OCI container images.



[(https://img.shields.io/github/license/outskirtslabs/datomic-pro-flake)]
`datomic-pro-flake` provides:
* Versioned `datomic-pro` and `datomic-pro-peer` nix packages.
* NixOS modules for running Datomic Pro transactor and Datomic Console.
* An OCI image (no nix requiredd!) for running transactor or console with environment variables and `_FILE` secrets.
All outputs are tested end-to-end in this repository.
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Quick Start
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
];
};
};
}
```
## Documentation
To learn how to configure the module and OCI container, see:
* [NixOS Module](nixos-module.adoc)
* [Docker/OCI Container](docker-oci-container.adoc)
* [Docs Home](https://docs.outskirtslabs.com/datomic-pro-flake/next/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/datomic-pro-flake/issues)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright © 2025-2026 Casey Link <casey@outskirtslabs.com>
Distributed under the [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html).
## NixOS Module
# NixOS Module
This flake provides NixOS modules for Datomic Pro transactor and Datomic Console.
## Add The Module
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
datomic-pro.url = "github:outskirtslabs/datomic-pro-flake";
datomic-pro.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, datomic-pro, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.machine = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
datomic-pro.nixosModules.${system}.datomic-pro
datomic-pro.nixosModules.${system}.datomic-console
];
};
};
}
```
## Secrets File
Create `/etc/datomic-pro/secrets.properties`:
```properties
storage-admin-password=changeme
storage-datomic-password=changeme
```
- ❗ IMPORTANT
-
Do not use `environment.etc` for secrets. That writes data to the nix store, which is globally readable and can be cached remotely.
Lock down permissions:
```shell
chown root:root /etc/datomic-pro/secrets.properties
chmod 0600 /etc/datomic-pro/secrets.properties
```
## Example Configuration
A basic dev-mode transactor storing data under `/var/lib/datomic-pro`:
```nix
{
services.datomic-pro = {
enable = true;
package = pkgs.datomic-pro_1_0_7622;
secretsFile = "/etc/datomic-pro/secrets.properties";
settings = {
host = "localhost";
port = 4334;
memory-index-max = "256m";
memory-index-threshold = "32m";
object-cache-max = "128m";
protocol = "dev";
storage-access = "remote";
};
};
services.datomic-console = {
enable = true;
port = 8080;
dbUriFile = "/etc/datomic-pro/console-db-uri";
};
}
```
## Available Packages
`pkgs.datomic-pro` and `pkgs.datomic-pro-peer` track the latest supported Datomic release.
Specific versions are also exposed, for example:
* `pkgs.datomic-pro_1_0_7622` (latest)
* `pkgs.datomic-pro_1_0_7556`
* `pkgs.datomic-pro_1_0_7491`
* `pkgs.datomic-pro_1_0_7482`
* `pkgs.datomic-pro_1_0_7469`
* `pkgs.datomic-pro_1_0_7394`
* `pkgs.datomic-pro_1_0_7387`
* `pkgs.datomic-pro_1_0_7364`
* `pkgs.datomic-pro_1_0_7277`
And for peer:
* `pkgs.datomic-pro-peer_1_0_7622` (latest)
* `pkgs.datomic-pro-peer_1_0_7556`
* `pkgs.datomic-pro-peer_1_0_7491`
* `pkgs.datomic-pro-peer_1_0_7482`
* `pkgs.datomic-pro-peer_1_0_7469`
* `pkgs.datomic-pro-peer_1_0_7394`
* `pkgs.datomic-pro-peer_1_0_7387`
* `pkgs.datomic-pro-peer_1_0_7364`
* `pkgs.datomic-pro-peer_1_0_7277`
New upstream Datomic releases are typically added within 24 hours.
## Security
# Security
Security issues should be reported privately.
Please review the Outskirts Labs policy first:
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy)
For this repository specifically:
* [Security advisories](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories)
* [Report a vulnerability](https://github.com/outskirtslabs/datomic-pro-flake/security/advisories/new)
## Changelog
# Changelog
All notable changes to fluent-tooling are documented here.
## 0.0.1 - 2026-07-13
Initial release of the Fluent grammar, linter, and Emacs tooling.
## Development
# Development
The repository provides a reproducible Nix shell with Rust, Tree-sitter, Babashka, Clang, Emacs, and Flycheck.
## Enter the development shell
```bash
nix develop
```
All commands below assume this shell unless shown with `nix develop --command`.
## Repository layout
| Path | Contents |
| --- | --- |
| `grammar.js`, `src/`, `queries/` | Grammar definition, generated parser, scanner, and queries |
| `crates/tree-sitter-fluent/` | Rust grammar bindings and parser tests |
| `crates/fluent-lint/` | Linter library, CLI, and tests |
| `editors/emacs/` | Major mode, Flymake adapter, Flycheck checker, and ERT tests |
| `test/fixtures/` | Upstream and project regression fixtures |
## Quality tasks
Babashka exposes the repository’s development tasks:
| Command | Action |
| --- | --- |
| `bb fmt` | Format Rust and grammar metadata sources |
| `bb fmt:check` | Check formatting without changing files |
| `bb lint` | Run Clippy with warnings denied |
| `bb test` | Run all Rust tests |
| `bb test:emacs` | Compile the grammar and run all ERT tests |
| `bb ci` | Run formatting, Clippy, Rust tests, and ERT tests |
Run the complete quality gate with:
```bash
bb ci
```
From outside the shell, use:
```bash
nix develop --command bb ci
```
## Regenerate the parser
After editing `grammar.js`, regenerate the checked-in Tree-sitter output:
```bash
bb generate
```
Review changes to `src/parser.c`, `src/grammar.json`, and `src/node-types.json` with the grammar change. Run `bb ci` after generation.
## Run the linter from Cargo
```bash
cargo run -p fluent-lint --bin ftl-lint -- \
test/fixtures/regressions/broken.ftl
```
## Test fixtures
The test suite adopts Project Fluent, Fluent.js, and fluent-rs fixtures in addition to project regressions. Imported fixtures retain exact line endings, trailing whitespace, and missing final newlines because those properties test parser behavior.
Record the source, revision, license, and local normalization of imported fixtures in [`test/fixtures/UPSTREAM.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/test/fixtures/UPSTREAM.md). Grammar and fixture license provenance belongs in [`NOTICE.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/NOTICE.md).
## Nix validation
Validate both packages and every flake check before release:
```bash
nix build
nix build .#tree-sitter-fluent
nix flake check
```
## Emacs integration
# Emacs integration
`fluent-ts-mode` is a native Tree-sitter major mode for Fluent FTL resources. It requires Emacs 29.1 or newer with built-in Tree-sitter support.
The mode registers `.ftl` files and provides syntax highlighting, four-space indentation, comments, Imenu, defun navigation, and syntax navigation. It can check unsaved buffer contents with built-in Flymake or optional Flycheck support through `ftl-lint`.
The Emacs package does not bundle a compiled grammar. Install the package and then install or expose the grammar explicitly. Opening an `.ftl` file never downloads or installs software.
## Install the package
### Vanilla Emacs with `package-vc`
Emacs 29.1 and newer can install the package directly from this repository:
```elisp
(package-vc-install
'(fluent-ts-mode
:url "https://github.com/outskirtslabs/fluent-tooling"
:lisp-dir "editors/emacs"
:main-file "fluent-ts-mode.el"))
(require 'fluent-ts-mode)
```
### straight.el
```elisp
(straight-use-package
'(fluent-ts-mode
:type git
:host github
:repo "outskirtslabs/fluent-tooling"
:files ("editors/emacs/*.el")))
```
### Doom Emacs
Add the package recipe to `packages.el`:
```elisp
(package! fluent-ts-mode
:recipe (:host github
:repo "outskirtslabs/fluent-tooling"
:files ("editors/emacs/*.el")))
```
Run `doom sync`, then load and configure the mode from `config.el` or a language module:
```elisp
(require 'fluent-ts-mode)
(setq fluent-ts-mode-checker 'auto)
```
### Local checkout
Load the package directly while developing it:
```elisp
(add-to-list 'load-path
"/absolute/path/to/fluent-tooling/editors/emacs")
(require 'fluent-ts-mode)
```
## Install the grammar
Choose one of the following methods. Install the grammar before opening an FTL file with `fluent-ts-mode`.
### Build from the public source recipe
Run `M-x fluent-ts-mode-install-grammar`. The command asks Emacs’s built-in Tree-sitter installer to clone this repository, compile `src/parser.c` and `src/scanner.c`, and install the resulting shared library. This command is the only part of `fluent-ts-mode` that may access the network.
With a prefix argument, the command prompts for an installation directory:
```text
C-u M-x fluent-ts-mode-install-grammar
```
### Build from a local checkout
Replace the public recipe after loading the mode, then run the installation command:
```elisp
(require 'fluent-ts-mode)
(setf (alist-get 'fluent treesit-language-source-alist)
'("/absolute/path/to/fluent-tooling"))
```
### Use the Nix grammar package
Build the grammar and add its library directory to Emacs’s search path:
```bash
nix build .#tree-sitter-fluent
```
```elisp
(require 'treesit)
(add-to-list 'treesit-extra-load-path
"/absolute/path/to/fluent-tooling/result/lib")
(require 'fluent-ts-mode)
```
Keep the `result` link available, or use the grammar’s immutable Nix store path. Evaluate `(treesit-language-available-p 'fluent)` to confirm that Emacs can load it.
## Configure diagnostics
`fluent-ts-mode-checker` controls diagnostics:
| Value | Behavior |
| --- | --- |
| `auto` | Use Flycheck when it is already loaded; otherwise use Flymake |
| `flymake` | Use built-in Flymake |
| `flycheck` | Require and use Flycheck |
| `nil` | Disable diagnostics |
The default is `auto`. Load Flycheck before opening an FTL buffer when you want automatic selection to choose it:
```elisp
(require 'flycheck)
(setq fluent-ts-mode-checker 'auto)
```
Both adapters send unsaved buffer contents to `ftl-lint --format json -`. Install `ftl-lint` on Emacs’s `exec-path`, or set its absolute path:
```elisp
(setq fluent-ts-mode-linter-executable
"/absolute/path/to/ftl-lint")
```
Select a frontend explicitly when needed:
```elisp
(setq fluent-ts-mode-checker 'flymake)
;; or
(setq fluent-ts-mode-checker 'flycheck)
```
The adapters consume the stable [`ftl-lint` JSON diagnostics protocol](json-diagnostics.adoc).
## Customize indentation
Fluent continuation lines use four spaces by default:
```elisp
(setq fluent-ts-mode-indent-offset 4)
```
## Troubleshooting
* If mode activation reports that the grammar is unavailable, install it or add its library directory to `treesit-extra-load-path` before opening the file.
* If diagnostics report that `ftl-lint` cannot be started, check `(executable-find "ftl-lint")` or set `fluent-ts-mode-linter-executable` explicitly.
* If `auto` selects Flymake, load Flycheck before activating the FTL buffer or set `fluent-ts-mode-checker` to `flycheck`.
## ftl-lint
# `ftl-lint`
`ftl-lint` checks Fluent FTL resources for syntax and structural problems. It uses the repository’s error-tolerant Tree-sitter grammar and reports source labels, Fluent error codes, notes, and actionable help.
## Build and run
Build the linter with Nix:
```bash
nix build .#ftl-lint
./result/bin/ftl-lint path/to/messages.ftl
```
Run it without creating a result link:
```bash
nix run .#ftl-lint -- path/to/messages.ftl
```
Build it directly with Cargo from this checkout:
```bash
cargo build --release -p fluent-lint --bin ftl-lint
./target/release/ftl-lint path/to/messages.ftl
```
## Inputs
Pass one or more files as separate arguments:
```bash
ftl-lint locales/en/messages.ftl locales/pl/messages.ftl
```
Use `-` once to read an FTL resource from standard input:
```bash
ftl-lint - < locales/en/messages.ftl
```
## Output
Human-readable diagnostics are the default and go to standard error. Color is enabled when standard error is a terminal, unless `NO_COLOR` is set. Override the choice with `--color always`, `--color never`, or `--no-color`.
Use `--format json` for editor and tool integration. JSON diagnostics go to standard output, while invocation and I/O errors remain on standard error. The [JSON diagnostics protocol](json-diagnostics.adoc) defines the stable integration interface.
## Options
| Option | Meaning |
| --- | --- |
| `--format ` | Select `human` or `json` output; defaults to `human` |
| `--color ` | Select `auto`, `always`, or `never`; defaults to `auto` |
| `--no-color` | Alias for `--color never` |
| `-h`, `--help` | Print command help |
| `-V`, `--version` | Print the version |
## Exit status
| Status | Meaning |
| --- | --- |
| `0` | Every input is clean |
| `1` | At least one diagnostic was reported |
| `2` | Invocation or I/O failure |
An I/O failure takes precedence over diagnostics from other inputs, so a multi-file run returns status 2 if any file cannot be read.
## Diagnostic codes
The linter emits these Project Fluent syntax codes where applicable:
| Code | Meaning |
| --- | --- |
| `E0002` | Invalid entry or syntax |
| `E0003` | Expected token |
| `E0005` | Message requires a value or attributes |
| `E0006` | Term requires a value |
| `E0010` | Missing default variant |
| `E0012` | Missing attribute value |
| `E0015` | Duplicate default variant |
| `E0019` | Term attribute used as a placeable |
| `E0020` | Unterminated string |
| `E0027` | Unbalanced closing brace |
`W1001` is a project lint that replaces redundant `PLURAL(...)` wrappers with native Fluent selection.
## fluent-tooling
# fluent-tooling
> Tree-sitter grammar, linter, and editor tooling for the [Fluent localization system](https://projectfluent.org/) from Mozilla.



This repository contains:
* [`ftl-lint`](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/ftl-lint), a command-line linter with compiler-style diagnostics;
* [`tree-sitter-fluent`](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/tree-sitter), an error-tolerant Tree-sitter grammar with Rust bindings; and
* [`fluent-ts-mode`](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/emacs), a native Tree-sitter major mode for Emacs.
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Quick start
Run the linter from this checkout:
```bash
nix run . -- path/to/messages.ftl
```
See the [`ftl-lint` guide](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/ftl-lint) for installation, command-line options, diagnostic codes, and editor integration.
## Documentation
* [Docs](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/)
* [API Reference](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/fluent-tooling/issues)
## Nix packages
The flake exports two named packages and selects `ftl-lint` by default:
| Command | Package |
| --- | --- |
| `nix build` or `nix build .#ftl-lint` | The `ftl-lint` executable |
| `nix build .#tree-sitter-fluent` | The compiled Tree-sitter grammar |
See the [`tree-sitter-fluent` guide](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/tree-sitter) for the grammar package’s layout and integration options.
## Development
Enter the reproducible development shell and run the complete quality gate:
```bash
nix develop
bb ci
```
The [development guide](https://docs.outskirtslabs.com/fluent-tooling/0.0.1/development) covers repository structure, parser generation, individual checks, and fixture provenance.
## Licensing
Copyright © 2017-present David Rios for the original Tree-sitter grammar.
Copyright © 2026 mailto:casey@outskirtslabs.com[Casey Link].
Distributed under the [MIT](https://spdx.org/licenses/MIT.html) license.
Copied grammar provenance and third-party fixture licenses are documented in [`NOTICE.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/NOTICE.md) and [`test/fixtures/UPSTREAM.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/test/fixtures/UPSTREAM.md).
## JSON diagnostics protocol
# JSON diagnostics protocol
`ftl-lint --format json` is the stable interface for editor and tool integration. Human-readable output may evolve independently; integrations should consume this protocol instead.
## Invocation and streams
Pass files or standard input exactly as in human mode:
```bash
ftl-lint --format json locales/pl/messages.ftl
ftl-lint --format json - < locales/pl/messages.ftl
```
A valid invocation writes one JSON document to standard output. Invocation and I/O errors go to standard error. The JSON document is compact; examples in this guide are formatted for readability.
## Schema version 1
```json
{
"schema_version": 1,
"diagnostics": [
{
"path": "locales/pl/messages.ftl",
"severity": "error",
"code": "E0003",
"message": "expected token `}`",
"labels": [
{
"primary": true,
"message": "unexpected `?` inside the placeable",
"span": {
"start": { "byte": 30, "line": 0, "column": 26 },
"end": { "byte": 31, "line": 0, "column": 27 }
}
}
],
"notes": [],
"help": ["close the placeable before `?`"]
}
]
}
```
The top-level object contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `schema_version` | integer | Protocol version; currently `1` |
| `diagnostics` | array | Zero or more diagnostic objects |
Each diagnostic contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `path` | string | Input path, or `` for standard input |
| `severity` | string | `error` or `warning` |
| `code` | string | Stable Fluent or project lint code |
| `message` | string | Diagnostic summary |
| `labels` | array | Labeled source spans |
| `notes` | array of strings | Supporting context |
| `help` | array of strings | Suggested actions |
Every label contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `primary` | boolean | Whether this is the diagnostic’s primary label |
| `message` | string | Explanation attached to the span |
| `span` | object | Half-open source range with `start` and `end` positions |
The `diagnostics`, `labels`, `notes`, and `help` arrays are always present, including when they are empty.
## Source positions
Each position contains three zero-based values:
| Field | Meaning |
| --- | --- |
| `byte` | UTF-8 byte offset from the beginning of the input |
| `line` | Line number |
| `column` | Unicode scalar-value offset from the beginning of the line |
Spans are half-open: `start` is inclusive and `end` is exclusive. Empty spans have identical start and end positions. LF, CRLF, and lone CR each advance the line once and reset the column to zero.
Columns count Unicode scalar values, not bytes, UTF-16 code units, grapheme clusters, or display cells. Consumers that index UTF-8 source can use `byte`; editors should translate `line` and `column` into their native position type.
## Exit and error behavior
The JSON format preserves the command’s normal exit statuses:
| Result | Standard output | Standard error | Status |
| --- | --- | --- | --- |
| All inputs clean | Empty diagnostics document | Empty | `0` |
| Diagnostics found | Diagnostics document | Empty | `1` |
| An input cannot be read | Diagnostics from successfully read inputs | I/O error | `2` |
| Invalid invocation | No JSON document | Error and usage | `2` |
An I/O failure may therefore produce an empty or partial diagnostics document. Consumers must check the process status and standard error as well as parse standard output.
## Compatibility
Consumers should reject documents whose `schema_version` they do not support. Breaking field or position changes require a new schema version. Consumers should ignore unknown fields within a supported version so compatible fields can be added later.
## tree-sitter-fluent
# `tree-sitter-fluent`
`tree-sitter-fluent` is an error-tolerant Tree-sitter grammar for Project Fluent FTL. It supports editors, linting, incremental parsing, and source-preserving tools.
## Build the grammar with Nix
```bash
nix build .#tree-sitter-fluent
```
The package contains:
```text
result/
├── parser
├── lib/
│ └── libtree-sitter-fluent.so -> ../parser
├── queries/
│ └── highlights.scm
└── tree-sitter.json
```
`parser` is the compiled shared library in the layout used by Nixpkgs Tree-sitter grammar packages. The `lib` link uses the platform’s shared-library extension—`.so` on Linux—and lets Emacs find the same library directly.
## Use the Rust bindings
The workspace crate exports a `tree-sitter-language` function and the generated node and highlight metadata:
```rust
use tree_sitter::Parser;
let mut parser = Parser::new();
let language = tree_sitter_fluent::LANGUAGE.into();
parser.set_language(&language)?;
```
The public constants are:
| Constant | Contents |
| --- | --- |
| `LANGUAGE` | Language function for `Parser::set_language` |
| `NODE_TYPES` | Generated `src/node-types.json` |
| `HIGHLIGHTS_QUERY` | `queries/highlights.scm` |
An external Cargo project can depend on the repository directly:
```toml
[dependencies]
tree-sitter = "0.25"
tree-sitter-fluent = { git = "https://github.com/outskirtslabs/fluent-tooling" }
```
## Grammar behavior
The CST exposes default variants as `default_variant` nodes, retains malformed regions as Tree-sitter errors, and recovers later valid entries. The grammar accepts LF, CRLF, and lone CR line endings, negative numeric literals, EOF comments, unindented block placeables, and deeply nested selectors.
The highlight query covers the syntax used by `fluent-ts-mode` and other Tree-sitter consumers. Tests compile the query against the generated grammar to keep captures synchronized with node types.
## Grammar sources
The repository keeps generated C sources under `src/` so consumers can compile the grammar without running the Tree-sitter generator:
| Path | Purpose |
| --- | --- |
| `grammar.js` | Authoritative grammar definition |
| `tree-sitter.json` | Grammar name, version, file types, and repository metadata |
| `src/parser.c` | Parser generated from `grammar.js` |
| `src/scanner.c` | Handwritten external scanner |
| `src/node-types.json` | Generated named-node metadata |
| `queries/highlights.scm` | Syntax-highlight captures |
Tree-sitter build tools conventionally look for `src/parser.c` and optionally `src/scanner.c` or `src/scanner.cc`. Emacs’s built-in installer follows that layout when it builds this repository’s public grammar recipe.
After editing `grammar.js`, regenerate the checked-in parser as described in the [development guide](development.adoc).
## Changelog
# Changelog
All notable changes to fluent-tooling are documented here.
## 0.0.1 - 2026-07-13
Initial release of the Fluent grammar, linter, and Emacs tooling.
## Development
# Development
The repository provides a reproducible Nix shell with Rust, Tree-sitter, Babashka, Clang, Emacs, and Flycheck.
## Enter the development shell
```bash
nix develop
```
All commands below assume this shell unless shown with `nix develop --command`.
## Repository layout
| Path | Contents |
| --- | --- |
| `grammar.js`, `src/`, `queries/` | Grammar definition, generated parser, scanner, and queries |
| `crates/tree-sitter-fluent/` | Rust grammar bindings and parser tests |
| `crates/fluent-lint/` | Linter library, CLI, and tests |
| `editors/emacs/` | Major mode, Flymake adapter, Flycheck checker, and ERT tests |
| `test/fixtures/` | Upstream and project regression fixtures |
## Quality tasks
Babashka exposes the repository’s development tasks:
| Command | Action |
| --- | --- |
| `bb fmt` | Format Rust and grammar metadata sources |
| `bb fmt:check` | Check formatting without changing files |
| `bb lint` | Run Clippy with warnings denied |
| `bb test` | Run all Rust tests |
| `bb test:emacs` | Compile the grammar and run all ERT tests |
| `bb ci` | Run formatting, Clippy, Rust tests, and ERT tests |
Run the complete quality gate with:
```bash
bb ci
```
From outside the shell, use:
```bash
nix develop --command bb ci
```
## Regenerate the parser
After editing `grammar.js`, regenerate the checked-in Tree-sitter output:
```bash
bb generate
```
Review changes to `src/parser.c`, `src/grammar.json`, and `src/node-types.json` with the grammar change. Run `bb ci` after generation.
## Run the linter from Cargo
```bash
cargo run -p fluent-lint --bin ftl-lint -- \
test/fixtures/regressions/broken.ftl
```
## Test fixtures
The test suite adopts Project Fluent, Fluent.js, and fluent-rs fixtures in addition to project regressions. Imported fixtures retain exact line endings, trailing whitespace, and missing final newlines because those properties test parser behavior.
Record the source, revision, license, and local normalization of imported fixtures in [`test/fixtures/UPSTREAM.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/test/fixtures/UPSTREAM.md). Grammar and fixture license provenance belongs in [`NOTICE.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/NOTICE.md).
## Nix validation
Validate both packages and every flake check before release:
```bash
nix build
nix build .#tree-sitter-fluent
nix flake check
```
## Emacs integration
# Emacs integration
`fluent-ts-mode` is a native Tree-sitter major mode for Fluent FTL resources. It requires Emacs 29.1 or newer with built-in Tree-sitter support.
The mode registers `.ftl` files and provides syntax highlighting, four-space indentation, comments, Imenu, defun navigation, and syntax navigation. It can check unsaved buffer contents with built-in Flymake or optional Flycheck support through `ftl-lint`.
The Emacs package does not bundle a compiled grammar. Install the package and then install or expose the grammar explicitly. Opening an `.ftl` file never downloads or installs software.
## Install the package
### Vanilla Emacs with `package-vc`
Emacs 29.1 and newer can install the package directly from this repository:
```elisp
(package-vc-install
'(fluent-ts-mode
:url "https://github.com/outskirtslabs/fluent-tooling"
:lisp-dir "editors/emacs"
:main-file "fluent-ts-mode.el"))
(require 'fluent-ts-mode)
```
### straight.el
```elisp
(straight-use-package
'(fluent-ts-mode
:type git
:host github
:repo "outskirtslabs/fluent-tooling"
:files ("editors/emacs/*.el")))
```
### Doom Emacs
Add the package recipe to `packages.el`:
```elisp
(package! fluent-ts-mode
:recipe (:host github
:repo "outskirtslabs/fluent-tooling"
:files ("editors/emacs/*.el")))
```
Run `doom sync`, then load and configure the mode from `config.el` or a language module:
```elisp
(require 'fluent-ts-mode)
(setq fluent-ts-mode-checker 'auto)
```
### Local checkout
Load the package directly while developing it:
```elisp
(add-to-list 'load-path
"/absolute/path/to/fluent-tooling/editors/emacs")
(require 'fluent-ts-mode)
```
## Install the grammar
Choose one of the following methods. Install the grammar before opening an FTL file with `fluent-ts-mode`.
### Build from the public source recipe
Run `M-x fluent-ts-mode-install-grammar`. The command asks Emacs’s built-in Tree-sitter installer to clone this repository, compile `src/parser.c` and `src/scanner.c`, and install the resulting shared library. This command is the only part of `fluent-ts-mode` that may access the network.
With a prefix argument, the command prompts for an installation directory:
```text
C-u M-x fluent-ts-mode-install-grammar
```
### Build from a local checkout
Replace the public recipe after loading the mode, then run the installation command:
```elisp
(require 'fluent-ts-mode)
(setf (alist-get 'fluent treesit-language-source-alist)
'("/absolute/path/to/fluent-tooling"))
```
### Use the Nix grammar package
Build the grammar and add its library directory to Emacs’s search path:
```bash
nix build .#tree-sitter-fluent
```
```elisp
(require 'treesit)
(add-to-list 'treesit-extra-load-path
"/absolute/path/to/fluent-tooling/result/lib")
(require 'fluent-ts-mode)
```
Keep the `result` link available, or use the grammar’s immutable Nix store path. Evaluate `(treesit-language-available-p 'fluent)` to confirm that Emacs can load it.
## Configure diagnostics
`fluent-ts-mode-checker` controls diagnostics:
| Value | Behavior |
| --- | --- |
| `auto` | Use Flycheck when it is already loaded; otherwise use Flymake |
| `flymake` | Use built-in Flymake |
| `flycheck` | Require and use Flycheck |
| `nil` | Disable diagnostics |
The default is `auto`. Load Flycheck before opening an FTL buffer when you want automatic selection to choose it:
```elisp
(require 'flycheck)
(setq fluent-ts-mode-checker 'auto)
```
Both adapters send unsaved buffer contents to `ftl-lint --format json -`. Install `ftl-lint` on Emacs’s `exec-path`, or set its absolute path:
```elisp
(setq fluent-ts-mode-linter-executable
"/absolute/path/to/ftl-lint")
```
Select a frontend explicitly when needed:
```elisp
(setq fluent-ts-mode-checker 'flymake)
;; or
(setq fluent-ts-mode-checker 'flycheck)
```
The adapters consume the stable [`ftl-lint` JSON diagnostics protocol](json-diagnostics.adoc).
## Customize indentation
Fluent continuation lines use four spaces by default:
```elisp
(setq fluent-ts-mode-indent-offset 4)
```
## Troubleshooting
* If mode activation reports that the grammar is unavailable, install it or add its library directory to `treesit-extra-load-path` before opening the file.
* If diagnostics report that `ftl-lint` cannot be started, check `(executable-find "ftl-lint")` or set `fluent-ts-mode-linter-executable` explicitly.
* If `auto` selects Flymake, load Flycheck before activating the FTL buffer or set `fluent-ts-mode-checker` to `flycheck`.
## ftl-lint
# `ftl-lint`
`ftl-lint` checks Fluent FTL resources for syntax and structural problems. It uses the repository’s error-tolerant Tree-sitter grammar and reports source labels, Fluent error codes, notes, and actionable help.
## Build and run
Build the linter with Nix:
```bash
nix build .#ftl-lint
./result/bin/ftl-lint path/to/messages.ftl
```
Run it without creating a result link:
```bash
nix run .#ftl-lint -- path/to/messages.ftl
```
Build it directly with Cargo from this checkout:
```bash
cargo build --release -p fluent-lint --bin ftl-lint
./target/release/ftl-lint path/to/messages.ftl
```
## Inputs
Pass one or more files as separate arguments:
```bash
ftl-lint locales/en/messages.ftl locales/pl/messages.ftl
```
Use `-` once to read an FTL resource from standard input:
```bash
ftl-lint - < locales/en/messages.ftl
```
## Output
Human-readable diagnostics are the default and go to standard error. Color is enabled when standard error is a terminal, unless `NO_COLOR` is set. Override the choice with `--color always`, `--color never`, or `--no-color`.
Use `--format json` for editor and tool integration. JSON diagnostics go to standard output, while invocation and I/O errors remain on standard error. The [JSON diagnostics protocol](json-diagnostics.adoc) defines the stable integration interface.
## Options
| Option | Meaning |
| --- | --- |
| `--format ` | Select `human` or `json` output; defaults to `human` |
| `--color ` | Select `auto`, `always`, or `never`; defaults to `auto` |
| `--no-color` | Alias for `--color never` |
| `-h`, `--help` | Print command help |
| `-V`, `--version` | Print the version |
## Exit status
| Status | Meaning |
| --- | --- |
| `0` | Every input is clean |
| `1` | At least one diagnostic was reported |
| `2` | Invocation or I/O failure |
An I/O failure takes precedence over diagnostics from other inputs, so a multi-file run returns status 2 if any file cannot be read.
## Diagnostic codes
The linter emits these Project Fluent syntax codes where applicable:
| Code | Meaning |
| --- | --- |
| `E0002` | Invalid entry or syntax |
| `E0003` | Expected token |
| `E0005` | Message requires a value or attributes |
| `E0006` | Term requires a value |
| `E0010` | Missing default variant |
| `E0012` | Missing attribute value |
| `E0015` | Duplicate default variant |
| `E0019` | Term attribute used as a placeable |
| `E0020` | Unterminated string |
| `E0027` | Unbalanced closing brace |
`W1001` is a project lint that replaces redundant `PLURAL(...)` wrappers with native Fluent selection.
## fluent-tooling
# fluent-tooling
> Tree-sitter grammar, linter, and editor tooling for the [Fluent localization system](https://projectfluent.org/) from Mozilla.



This repository contains:
* [`ftl-lint`](https://docs.outskirtslabs.com/fluent-tooling/next/ftl-lint), a command-line linter with compiler-style diagnostics;
* [`tree-sitter-fluent`](https://docs.outskirtslabs.com/fluent-tooling/next/tree-sitter), an error-tolerant Tree-sitter grammar with Rust bindings; and
* [`fluent-ts-mode`](https://docs.outskirtslabs.com/fluent-tooling/next/emacs), a native Tree-sitter major mode for Emacs.
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Quick start
Run the linter from this checkout:
```bash
nix run . -- path/to/messages.ftl
```
See the [`ftl-lint` guide](https://docs.outskirtslabs.com/fluent-tooling/next/ftl-lint) for installation, command-line options, diagnostic codes, and editor integration.
## Documentation
* [Docs](https://docs.outskirtslabs.com/fluent-tooling/next/)
* [API Reference](https://docs.outskirtslabs.com/fluent-tooling/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/fluent-tooling/issues)
## Nix packages
The flake exports two named packages and selects `ftl-lint` by default:
| Command | Package |
| --- | --- |
| `nix build` or `nix build .#ftl-lint` | The `ftl-lint` executable |
| `nix build .#tree-sitter-fluent` | The compiled Tree-sitter grammar |
See the [`tree-sitter-fluent` guide](https://docs.outskirtslabs.com/fluent-tooling/next/tree-sitter) for the grammar package’s layout and integration options.
## Development
Enter the reproducible development shell and run the complete quality gate:
```bash
nix develop
bb ci
```
The [development guide](https://docs.outskirtslabs.com/fluent-tooling/next/development) covers repository structure, parser generation, individual checks, and fixture provenance.
## Licensing
Copyright © 2017-present David Rios for the original Tree-sitter grammar.
Copyright © 2026 mailto:casey@outskirtslabs.com[Casey Link].
Distributed under the [MIT](https://spdx.org/licenses/MIT.html) license.
Copied grammar provenance and third-party fixture licenses are documented in [`NOTICE.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/NOTICE.md) and [`test/fixtures/UPSTREAM.md`](https://github.com/outskirtslabs/fluent-tooling/blob/main/test/fixtures/UPSTREAM.md).
## JSON diagnostics protocol
# JSON diagnostics protocol
`ftl-lint --format json` is the stable interface for editor and tool integration. Human-readable output may evolve independently; integrations should consume this protocol instead.
## Invocation and streams
Pass files or standard input exactly as in human mode:
```bash
ftl-lint --format json locales/pl/messages.ftl
ftl-lint --format json - < locales/pl/messages.ftl
```
A valid invocation writes one JSON document to standard output. Invocation and I/O errors go to standard error. The JSON document is compact; examples in this guide are formatted for readability.
## Schema version 1
```json
{
"schema_version": 1,
"diagnostics": [
{
"path": "locales/pl/messages.ftl",
"severity": "error",
"code": "E0003",
"message": "expected token `}`",
"labels": [
{
"primary": true,
"message": "unexpected `?` inside the placeable",
"span": {
"start": { "byte": 30, "line": 0, "column": 26 },
"end": { "byte": 31, "line": 0, "column": 27 }
}
}
],
"notes": [],
"help": ["close the placeable before `?`"]
}
]
}
```
The top-level object contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `schema_version` | integer | Protocol version; currently `1` |
| `diagnostics` | array | Zero or more diagnostic objects |
Each diagnostic contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `path` | string | Input path, or `` for standard input |
| `severity` | string | `error` or `warning` |
| `code` | string | Stable Fluent or project lint code |
| `message` | string | Diagnostic summary |
| `labels` | array | Labeled source spans |
| `notes` | array of strings | Supporting context |
| `help` | array of strings | Suggested actions |
Every label contains:
| Field | Type | Meaning |
| --- | --- | --- |
| `primary` | boolean | Whether this is the diagnostic’s primary label |
| `message` | string | Explanation attached to the span |
| `span` | object | Half-open source range with `start` and `end` positions |
The `diagnostics`, `labels`, `notes`, and `help` arrays are always present, including when they are empty.
## Source positions
Each position contains three zero-based values:
| Field | Meaning |
| --- | --- |
| `byte` | UTF-8 byte offset from the beginning of the input |
| `line` | Line number |
| `column` | Unicode scalar-value offset from the beginning of the line |
Spans are half-open: `start` is inclusive and `end` is exclusive. Empty spans have identical start and end positions. LF, CRLF, and lone CR each advance the line once and reset the column to zero.
Columns count Unicode scalar values, not bytes, UTF-16 code units, grapheme clusters, or display cells. Consumers that index UTF-8 source can use `byte`; editors should translate `line` and `column` into their native position type.
## Exit and error behavior
The JSON format preserves the command’s normal exit statuses:
| Result | Standard output | Standard error | Status |
| --- | --- | --- | --- |
| All inputs clean | Empty diagnostics document | Empty | `0` |
| Diagnostics found | Diagnostics document | Empty | `1` |
| An input cannot be read | Diagnostics from successfully read inputs | I/O error | `2` |
| Invalid invocation | No JSON document | Error and usage | `2` |
An I/O failure may therefore produce an empty or partial diagnostics document. Consumers must check the process status and standard error as well as parse standard output.
## Compatibility
Consumers should reject documents whose `schema_version` they do not support. Breaking field or position changes require a new schema version. Consumers should ignore unknown fields within a supported version so compatible fields can be added later.
## tree-sitter-fluent
# `tree-sitter-fluent`
`tree-sitter-fluent` is an error-tolerant Tree-sitter grammar for Project Fluent FTL. It supports editors, linting, incremental parsing, and source-preserving tools.
## Build the grammar with Nix
```bash
nix build .#tree-sitter-fluent
```
The package contains:
```text
result/
├── parser
├── lib/
│ └── libtree-sitter-fluent.so -> ../parser
├── queries/
│ └── highlights.scm
└── tree-sitter.json
```
`parser` is the compiled shared library in the layout used by Nixpkgs Tree-sitter grammar packages. The `lib` link uses the platform’s shared-library extension—`.so` on Linux—and lets Emacs find the same library directly.
## Use the Rust bindings
The workspace crate exports a `tree-sitter-language` function and the generated node and highlight metadata:
```rust
use tree_sitter::Parser;
let mut parser = Parser::new();
let language = tree_sitter_fluent::LANGUAGE.into();
parser.set_language(&language)?;
```
The public constants are:
| Constant | Contents |
| --- | --- |
| `LANGUAGE` | Language function for `Parser::set_language` |
| `NODE_TYPES` | Generated `src/node-types.json` |
| `HIGHLIGHTS_QUERY` | `queries/highlights.scm` |
An external Cargo project can depend on the repository directly:
```toml
[dependencies]
tree-sitter = "0.25"
tree-sitter-fluent = { git = "https://github.com/outskirtslabs/fluent-tooling" }
```
## Grammar behavior
The CST exposes default variants as `default_variant` nodes, retains malformed regions as Tree-sitter errors, and recovers later valid entries. The grammar accepts LF, CRLF, and lone CR line endings, negative numeric literals, EOF comments, unindented block placeables, and deeply nested selectors.
The highlight query covers the syntax used by `fluent-ts-mode` and other Tree-sitter consumers. Tests compile the query against the generated grammar to keep captures synchronized with node types.
## Grammar sources
The repository keeps generated C sources under `src/` so consumers can compile the grammar without running the Tree-sitter generator:
| Path | Purpose |
| --- | --- |
| `grammar.js` | Authoritative grammar definition |
| `tree-sitter.json` | Grammar name, version, file types, and repository metadata |
| `src/parser.c` | Parser generated from `grammar.js` |
| `src/scanner.c` | Handwritten external scanner |
| `src/node-types.json` | Generated named-node metadata |
| `queries/highlights.scm` | Syntax-highlight captures |
Tree-sitter build tools conventionally look for `src/parser.c` and optionally `src/scanner.c` or `src/scanner.cc`. Emacs’s built-in installer follows that layout when it builds this repository’s public grammar recipe.
After editing `grammar.js`, regenerate the checked-in parser as described in the [development guide](development.adoc).
## h2o-zig
# h2o-zig
> libh2o packaged for Zig with cross-compilation support for Linux and
> macOS.
[(https://img.shields.io/badge/doc-outskirtslabs-orange.svg)]
https://docs.outskirtslabs.com/open-source-vital-signs#maturing[image:https://img.shields.io/badge/status-maturing-blue.svg[status:
maturing]]

This is [libh2o](https://h2o.examp1e.net/), packaged for Zig with
cross-compilation support for Linux and macos.
* all dependencies are statically linked into the output
* output is a static library (`.a`) for embedding into other projects
The intended usage is for building language bindings and FFI wrappers
that need to expose all h2o and SSL symbols in a final shared library.
Included h2o features:
* http 1
* http 2
* picotls
* quic {plus} http3
* brotli
* zstd
* [libaegis](https://github.com/aegis-aead/libaegis) for
[draft-irtf-cfrg-aegis-aead-18](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/)
features explicitly excluded:
* mruby
* memcached integration
* redis integration
* libuv
Supported targets:
* `x86++_++64-linux`
* `aarch64-linux`
* `x86++_++64-macos`
* `aarch64-macos`
Project status:
**[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Quick start
1. Install [zig](https://ziglang.org/)
2. `zig build` or `zig build -Dtarget=++<++target++>++` (where
`+<++target++>+` is from the above list)
## Documentation
* [Docs](https://docs.outskirtslabs.com/h2o-zig/next/)
* [API Reference](https://docs.outskirtslabs.com/h2o-zig/next/api)
* https://github.com/outskirtslabs/h2o-zig/issues[Support via GitHub
Issues]
## Prerequisites
You need the following installed:
* [Zig](https://ziglang.org/) 0.16.0
* Perl (for an h2o build step)
If you have nix you can use the dev shell provided by the flake in this
repo.
## Use as a dependency
First, update your `build.zig.zon`:
```
zig init # if you don't have a build.zig already
zig fetch --save git+https://github.com/outskirtslabs/h2o-zig.git
```
You can then import `h2o` in your `build.zig` with:
```zig
const h2o_dependency = b.dependency("h2o", .{
.target = target,
.optimize = optimize,
});
your_exe.linkLibrary(h2o_dependency.artifact("h2o-evloop"));
```
### Build Options
The following build-time flags are available:
* `-Duse-boringssl=++<++bool++>++` - Use BoringSSL instead of OpenSSL
(default: `true`)
Brotli and Zstandard support are always enabled through external Zig
dependencies built with position-independent code.
Redis and memcached integrations are not supported by this Zig package
and must not be relied upon. Some related upstream sources are compiled
into the archive to avoid maintaining downstream patches.
Example usage:
```zig
const h2o_dependency = b.dependency("h2o", .{
.target = target,
.optimize = optimize,
.@"use-boringssl" = true, // default
});
your_exe.linkLibrary(h2o_dependency.artifact("h2o-evloop"));
```
Or via command line when building:
```bash
zig build -Duse-boringssl=true
```
### Cross-Compilation to macOS
When cross-compiling from Linux to macOS targets (x86++_++64-macos or
aarch64-macos), the build requires the `APPLE++_++SDK++_++PATH`
environment variable to be set. This points to the macOS SDK that
provides system headers and libraries.
**Using Nix (Recommended)**
The provided nix flake automatically sets up `APPLE++_++SDK++_++PATH`
when you enter the development shell:
```bash
nix develop
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-macos
```
**Manual Setup**
If not using nix, you’ll need to obtain a macOS SDK and set the
environment variable:
```bash
export APPLE_SDK_PATH=/path/to/MacOSX.sdk
zig build -Dtarget=x86_64-macos
```
The SDK must contain `usr/include` with macOS system headers. Without
this, cross-compilation to macOS will fail with an error about the
missing `APPLE++_++SDK++_++PATH` environment variable.
**Note**: Cross-compilation to macOS from macOS does not require
`APPLE++_++SDK++_++PATH` as the system SDK is used automatically.
## Hacking on H2O
This project also serves as a reproducible dev environment for h2o
thanks to the nix flake’s devshell.
Simply activate the nix devshell then:
```bash
git clone https://github.com/h2o/h2o.git h2o
cd h2o
cmake -B build -S . -DDISABLE_LIBUV=ON -DWITH_MRUBY=OFF
cmake --build build -j$(nproc)
cmake --build build --target check
```
### Building picotls (with AEGIS support)
To build and test picotls (the TLS library used by h2o) with AEGIS
cipher support:
```bash
git clone https://github.com/h2o/picotls.git vendor/picotls
cd vendor/picotls
nix develop ../ # enter devshell from picotls directory
cmake -B build -S . \
-DWITH_AEGIS=ON \
-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH \
-DAEGIS_INCLUDE_DIR=$AEGIS_INCLUDE_DIR
cmake --build build -j$(nproc)
./build/test-openssl.t
```
## License: MIT License
h2o-zig is distributed under the
[MIT](https://spdx.org/licenses/MIT.html).
Copyright © 2025 Casey Link casey@outskirtslabs.com
Binary distributions (JAR files on Clojars and GitHub releases) may
bundle the following third-party projects:
* [h2o](https://github.com/h2o/h2o) is licensed under the MIT License and
copyright [DeNA Co., Ltd.](https://dena.com/),
[Kazuho Oku](https://github.com/kazuho/), and contributors.
* [brotli](https://github.com/google/brotli) is licensed under the MIT
License and copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
* [zstd](https://github.com/facebook/zstd) is licensed under the BSD
License and copyright (c) Meta Platforms, Inc.
* [OpenSSL](https://github.com/openssl/openssl) is licensed under the
Apache 2.0 License and copyright (c) 1998-2025 The OpenSSL Project
Authors, and copyright (c) 1995-1998 Eric A. Young, Tim J. Hudson.
* [BoringSSL](https://github.com/google/boringssl) is licensed under the
Apache 2.0 License and copyright
https://github.com/google/boringssl/blob/58da9b0d721fd807279f4e3898741c92cf43bdbd/AUTHORS#[a
bunch of folks]
* [libaegis](https://github.com/aegis-aead/libaegis) is licensed under the
MIT license and copyright (c) 2023-2026 Frank Denis
## Security policy
# Security policy
## Advisories
All security advisories will be posted
https://github.com/outskirtslabs/h2o-zig/security/advisories[on
GitHub].
## Reporting a vulnerability
Please report possible security vulnerabilities
https://github.com/outskirtslabs/h2o-zig/security/advisories[via
GitHub], or by emailing me at `casey@outskirtslabs.com`. You may encrypt
emails with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
— [Casey Link](https://casey.link)
## Outskirts Labs Developer Docs
# Outskirts Labs Developer Docs
## Hello
Welcome to Outskirts Labs, where I organize my active and maintained open source projects.
My personal GitHub is cluttered with experiments, forks, and random contributions. This space is for the projects I actively maintain.
I am an independent software consultant, serving clients at the edge of technical capacity, sustainable funding, and mainstream attention.
My work focuses on building tools that last without requiring an army of developers or regular rewrites.
If you're a small business, non-profit, or social enterprise needing custom software that fits both your mission and budget, check out outskirtslabs.com or get in touch directly casey@outskirtslabs.com
p.s. For the curious and fellow Clojurians, my personal homepage at casey.link may be of interest.
## Continuous, rapid, NixOS deployments to Hetzner Cloud
# Continuous, rapid, NixOS deployments to Hetzner Cloud
OpenTofu.
>
> [(https://img.shields.io/badge/doc-outskirtslabs-orange.svg)]
> https://docs.outskirtslabs.com/open-source-vital-signs#static[image:https://img.shields.io/badge/status-static-blue.svg[status:
> static]]
> 
>
> - 📌 NOTE
-
> This repo was based on https://determinate.systems[Determinate
> Systems]'s https://github.com/determinatesystems/demo[repo for AWS
> AMIs]. This is a proof-of-concept repo maintained by me and not DetSys.
> I use something like this in prod, so it works, but don’t expect this
> repo to be updated regularly.
>
>
> This project shows you how to continuously deploy a
> [NixOS](https://zero-to-nix.com/concepts/nixos) configuration to a
> [Hetzner Cloud](https://www.hetzner.com/cloud) server using
> [OpenTofu](https://opentofu.org) and [FlakeHub](https://flakehub.com) in
> seconds.
>
> == Differences from the AWS demo
>
> This repo is a port of the
> [Determinate Systems AWS demo](https://github.com/determinatesystems/demo)
> to Hetzner Cloud. Key differences:
>
> | | | |
> | --- | --- | --- |
> | Aspect | AWS Demo | Hetzner Demo |
> | Demo app | EtherCalc (removed from nixpkgs) | CryptPad |
> | Base image | Pre-built AMIs from Determinate Systems | Custom image built from [nixos-hetzner](https://github.com/outskirtslabs/nixos-hetzner) and uploaded |
> | FlakeHub auth | IAM role (`determinate-nixd login aws`) | API token (`determinate-nixd login token`) |
> | Deployment method | AWS Systems Manager (SSM) | SSH with deploy key |
> | Networking | VPC, subnets, security groups | Simple firewall rules |
> | GitHub auth | OIDC federation with AWS | SSH deploy key as secret |
>
> The core FlakeHub workflow remains the same: build closures in CI,
> publish to FlakeHub Cache, and apply pre-built configurations in
> seconds.
>
> However there are a few caveats:
>
> Since Hetzner Cloud does not have a public marketplace for cloud images,
> you have to build the cloud image yourself (with the help of
> [nixos-hetzner](https://github.com/outskirtslabs/nixos-hetzner)) and
> upload it. Hetzner Cloud API keys are scoped to a project, so you will
> need to upload the image on a per-project basis.
>
> We rely on the tool
> [hcloud-upload-image](https://github.com/apricote/hcloud-upload-image) to
> perform the image creation. It can take awhile. In my experience
> uploading a 4GB image can take 8-12 minutes (assuming a fast pipe and
> bandwidth is not the bottleneck).
>
> If you build the image on a system using FlakeHub Cache, then the cloud
> image will be available to other systems from which you want to deploy
> (that are also using FlakeHub Cache).
>
> Once you have a NixOS image uploaded to HCloud, it is straightforward to
> create a nixos server from the snapshot. You can clickops at
> console.hetzner.com, use the hcloud cli tool, or use terraform/opentofu.
>
> In this demo we use opentofu to provision the server, firewall, ssh key,
> etc.
>
> The deployment process involves fetching a pre-built NixOS
> [closure](https://zero-to-nix.com/concepts/closures) from
> [FlakeHub](https://flakehub.com) and applying it to the Hetzner Cloud
> server, streamlining the deployment process and ensuring consistency
> across deployments.
>
> Timings (YMMV):
>
> | | |
> | --- | --- |
> | Step | Time |
> | Image upload | ~9 minutes |
> | Initial tofu apply | ~4 minutes |
> | - Hetzner resources creation | 3m5s |
> | - FlakeHub nixos provisioning | ~60s |
> | Subsequent deploy (GitHub Actions) | ~3 minutes |
> | - Build {plus} publish | 2m55s |
> | - fh apply via SSH | 14s |
>
> == Sign-up for the FlakeHub beta
>
> To experience this streamlined NixOS deployment pipeline for yourself,
> [sign up for the FlakeHub beta](https://determinate.systems) at
> https://determinate.systems. FlakeHub provides the enterprise-grade Nix
> infrastructure needed to fully use these advanced deployment techniques.
>
> == Prerequisites
>
> * Paid [Hetzner Cloud account](https://www.hetzner.com/cloud) with an API
> token
> * Paid [FlakeHub account](https://flakehub.com) with an API token
> * [Detsys Nix](https://docs.determinate.systems/determinate-nix) with
> flakes enabled
> * [OpenTofu](https://opentofu.org) (available in the dev shell)
> Project status:
> **[Static](https://docs.outskirtslabs.com/open-source-vital-signs#static)**.
>
> == Getting Started
>
> This demo deploys [CryptPad](https://cryptpad.net), a collaborative
> document editing platform, to a Hetzner Cloud server.
>
> Deployment is a two-step process:
>
> 1. [Manual deployment](#manual-deployment) - Run OpenTofu locally to
> create the server infrastructure (required first)
> 2. [Automated deployment](#automated-deployment-with-github-actions) -
> Once the server exists, push to GitHub for CI/CD deployments
> - 💡 TIP
-
> For a full rundown of how everything in the demo works, see
> [How it works](#how-it-works) below.
>
>
> === Manual deployment
>
> ==== 1. Build and upload the base image
>
> First, build and upload a NixOS base image to Hetzner Cloud. This only
> needs to be done once (or when you want to update the base image).
>
> ```shell
> # Enter the dev shell
> nix develop
>
> # Create .env file with your tokens (keeps them out of shell history)
> cat > .env << 'EOF'
> HCLOUD_TOKEN=your-hetzner-token
> FLAKEHUB_TOKEN=your-flakehub-token
> EOF
> chmod 600 .env
>
> # Load and export tokens, then build/upload the image
> set -a && source .env && set +a
> ./scripts/upload-image.sh
>
> # Note the image ID from the output (e.g., 123456789)
> ```
>
> ==== 2. Generate a deploy key
>
> Generate an SSH key for GitHub Actions deployments:
>
> ```shell
> ssh-keygen -t ed25519 -C "github-actions-deploy" -f deploy_key -N ""
> ```
>
> ==== 3. Configure OpenTofu
>
> ```shell
> cd setup
>
> # Copy the example config
> cp terraform.tfvars.example terraform.tfvars
>
> # Edit terraform.tfvars with your values:
> # - hcloud_token: Your Hetzner Cloud API token
> # - hcloud_image_id: The image ID from step 1
> # - flakehub_token: Your FlakeHub API token
> # - deploy_ssh_public_key: Contents of deploy_key.pub
> # - deploy_ssh_private_key: Contents of deploy_key (private key, for first-boot provisioning)
> # - (optional) ssh_public_key: Your personal SSH key for manual access
> ```
>
> ==== 4. Create the infrastructure
>
> ```shell
> # Initialize OpenTofu
> tofu init
>
> # Validate the configuration
> tofu validate
>
> # Create the resources
> tofu apply -auto-approve
>
> # Get the website URL
> export CRYPTPAD_URL=$(tofu output --json | jq -r .website.value)
>
> # Open the website (wait ~60 seconds for first deployment)
> open "${CRYPTPAD_URL}"
>
> # (optional) When you're done, destroy the resources
> tofu destroy -auto-approve
> ```
>
> === Automated deployment with GitHub Actions
>
> Once the server exists, GitHub Actions can automatically deploy NixOS
> configuration changes. Configure the following repository secrets:
>
> | | |
> | --- | --- |
> | Secret | Description |
> | `DEPLOY++_++SSH++_++PRIVATE++_++KEY` | The contents of the `deploy++_++key` file (private key) |
> | `HETZNER++_++SERVER++_++IP` | The server IP from `tofu output server++_++ip` |
>
> Using the `gh` CLI after completing link:#manual-deployment[manual
> deployment]:
>
> ```shell
> # Create a production environment (optional)
> gh api repos/{owner}/{repo}/environments/production --method PUT
>
> # Set secrets
> cat deploy_key | gh secret set DEPLOY_SSH_PRIVATE_KEY
> tofu output -raw server_ip | gh secret set HETZNER_SERVER_IP
> ```
>
> The workflow will automatically build, publish to FlakeHub, and deploy
> on pushes to main.
>
> == Documentation
>
> * [Docs](https://docs.outskirtslabs.com/nixos-hetzner-demo/next/)
> * https://docs.outskirtslabs.com/nixos-hetzner-demo/next/api[API
> Reference]
> * https://github.com/outskirtslabs/nixos-hetzner-demo/issues[Support via
> GitHub Issues]
> == How it works
>
> Here’s a high level walkthrough of what’s going on
>
> Initial setup (one-time)
>
> 1. Build NixOS base image locally using
> [nixos-hetzner](https://github.com/outskirtslabs/nixos-hetzner)
> 2. Upload image to Hetzner Cloud via hcloud-upload-image
> 3. Generate SSH deploy key for GitHub Actions
> 4. Configure terraform.tfvars with tokens, image ID, and keys
> First deployment (tofu apply)
>
> 1. OpenTofu creates Hetzner resources (SSH key, firewall, server)
> 2. Server boots with base NixOS image (includes determinate-nixd and fh)
> 3. Provisioner SSHs into server
> 4. Authenticates with FlakeHub using `determinate-nixd login token`
> 5. Runs `fh apply nixos ++<++flakeref++>++` to pull config from FlakeHub
> 6. Server downloads pre-built closure and switches to new configuration
> Subsequent deployments (via GitHub Actions)
>
> 1. Developer pushes changes to flake.nix
> 2. CI builds the NixOS closure
> 3. flakehub-push publishes closure to FlakeHub (with output paths cached)
> 4. Deploy job SSHs to server with the exact flakeref
> 5. Server runs `fh apply nixos` - downloads closure from FlakeHub Cache
> 6. Configuration applied in seconds (no building on server)
> === Nix flake
>
> The [`flake.nix`](./flake.nix) defines the NixOS configuration for
> the demo system:
>
> * Inputs:
> * `nixpkgs`: Custom nixpkgs fork with Hetzner Cloud tools (can be
> reverted to upstream nixpkgs once
> [PR #375551](https://github.com/NixOS/nixpkgs/pull/375551) is merged
> * [`nixos-hetzner`](https://github.com/outskirtslabs/nixos-hetzner):
> Hetzner Cloud image building tools
> * `determinate`: Determinate Nix distribution from FlakeHub
> * `fh`: FlakeHub CLI from FlakeHub
> * Outputs:
> * `nixosConfigurations.cryptpad-demo`: The CryptPad server
> configuration
> * `devShells.default`: Development environment with required tools
> === OpenTofu configuration
>
> The [`setup/`](./setup/) directory contains OpenTofu configuration
> for Hetzner Cloud:
>
> * `providers.tf`: Hetzner Cloud provider configuration
> * `variables.tf`: Input variables (API tokens, image ID, SSH keys, etc.)
> * `main.tf`: Server, firewall, and SSH key resources
> * `outputs.tf`: Server IP and website URL
> After server creation, Terraform provisions the server via SSH:
>
> 1. Authenticates with FlakeHub using `determinate-nixd login token`
> 2. Applies the NixOS configuration using `fh apply nixos`
> === GitHub Actions workflow
>
> The [`.github/workflows/ci.yml`](./.github/workflows/ci.yml)
> workflow:
>
> 1. Build job: Builds the NixOS closure and publishes to FlakeHub
> 2. Deploy job: SSHs to the server and runs `fh apply nixos` with the new
> closure
> === Continuous deployment
>
> Continuous deployments work by:
>
> 1. Pushing changes to the `flake.nix`
> 2. GitHub Actions builds the new closure
> 3. The closure is published to FlakeHub Cache
> 4. The deploy job SSHs to the server and applies the new configuration
> To demonstrate, make a change to the CryptPad configuration in
> `flake.nix` and push the changes.
>
> === Triggering rollbacks
>
> Use the `workflow++_++dispatch` event to manually trigger a deployment
> of a previous version.
>
> Why FlakeHub?
>
> Applying fully evaluated NixOS closures via
> [FlakeHub](https://flakehub.com) differs from typical deployments using
> Nix in several key ways:
>
> _Deployment speed_
>
> * FlakeHub deployment: The NixOS configuration is evaluated and built
> ahead of time. As the closure is pre-built and cached, the deployment
> process is faster. The server only needs to download and apply the
> pre-built closure.
> * Typical Nix deployment: The evaluation and build process happens
> during deployment, which can be time-consuming.
> _Resource utilization_
>
> * FlakeHub deployment: Offloads the computationally intensive tasks of
> evaluation and building to a controlled environment (e.g., a CI/CD
> pipeline), freeing up resources on the target server.
> * Typical Nix deployment: The target server must handle the evaluation
> and build process, which can be resource-intensive.
> _Scalability_
>
> * FlakeHub deployment: The pre-built and cached nature allows for rapid
> instance provisioning, making it ideal for auto-scaling scenarios.
> * Typical Nix deployment: The time required for evaluation and building
> on each new instance can introduce significant delays.
> In summary, applying a fully evaluated NixOS closure from
> [FlakeHub](https://flakehub.com) ensures that the exact same configuration
> is deployed every time, as the closure is a fixed, immutable artifact.
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
Git tags and Github release use the tag of the Determinate Nix version
used in the VM.
We use the `epoch` value to indicate breaking changes to the Hetzner
Cloud image NixOS configuration.
## ++[++UNRELEASED++]++
* no changes yet
## epoch-1 (2025-xx-xx)
This is an initial version of NixOS with Determinate Nix for Hetzner
Cloud.
## Contributing
# Contributing
Contributions are more than welcome!
For organization-wide expectations, see
[Outskirts Labs Contributing Guide](https://docs.outskirtslabs.com/contributing-guide).
A Nix devshell is available, which includes all development
dependencies, for contributing to nixos-hetzner.
Enter this shell with:
```sh
nix develop
```
If you use [`direnv`](https://direnv.net/), just run `direnv allow` and
you will be dropped in this devShell.
## Building
```bash
# Build a disk image to ./result/
nix build #diskImages.x86_64-linux.hetzner
# Set your HCLOUD_TOKEN
# ref: https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/
export HCLOUD_TOKEN=..
nix run #.apps.x86_64-linux.smoke-test -- --image-path result/nixos-image*img --architecture x86_64
nix run #.apps.x86_64-linux.smoke-test -- --image-path result/nixos-image*img --architecture x86_64 --keep-image-on-failure --debug
```
## Code quality
```bash
# Run the formatters
nix fmt
# Run the linters
nix flake check --print-build-logs
```
## Releasing
Releases are automated via Github Actions. Every detsys nix release
results in a new image.
Git tags and Github release use the tag of the Determinate Nix version
used in the VM.
However, we use the `epoch` value to indicate breaking changes to the
Hetzner Cloud image NixOS configuration.
## NixOS with Determinate Nix for Hetzner Cloud
# NixOS with Determinate Nix for Hetzner Cloud
>
> [(https://img.shields.io/badge/doc-outskirtslabs-orange.svg)]
> https://docs.outskirtslabs.com/open-source-vital-signs#static[image:https://img.shields.io/badge/status-static-blue.svg[status:
> static]]
> 
>
> Hetzner is a price-competitive and conceptually simpler alternative to
> AWS and the other hyperscalers for the small orgs and teams that
> [I tend to work with](https://outskirtslabs.com).
>
> Using NixOS on Hetzner has traditionally been a bear, because Hetzner
> does not provide a NixOS image nor a straightforward way to create one.
> Most folks resort to using nixos-infect, nixos-anywhere to transmogrify
> a debian/ubuntu instance into NixOS.
>
> However several developments over the past year have changed the status
> quo:
>
> 1. [hcloud-upload-image](https://github.com/apricote/hcloud-upload-image)
> was released, a simple golang tool that takes a disk image as input and
> sideffects Hetzner Cloud in such a way that it creates a Snapshot from
> said image
> 2. [PR #375551](https://github.com/NixOS/nixpkgs/pull/375551) is making its
> way into nixpkgs which brings in `hcloud-upload-image` as well as the
> NixOS plumbing needed to produce hetzner images.
> 3. [FlakeHub Cache](https://flakehub.com/cache), available since late 2024,
> makes it _blazing_ fast to copy built closures into a running system.
> To be clear: I was not responsible for any of this. I’m taking advantage
> of the open-source efforts of others. This repo takes these disparate
> pieces and ties them together into an out-of-the-box solution for
> building Hetzner Cloud NixOS images.
>
> (and yes, even this repo is a derivative as I based it on
> [Determinate Systems](https://determinate.systems)'s
> [repo for AWS AMIs](https://github.com/DeterminateSystems/nixos-amis))
>
> '''''
>
> - 📌 NOTE
-
> This is a proof-of-concept repo maintained by me and not DetSys. I use
> something like this in prod, so what is here works, however don’t count
> on me to provide the same maintenance and upkeep like DetSys does for
> their official AWS AMIs.
>
>
> This repo makes available NixOS Hetzner Cloud images containing
> [Determinate Nix](https://docs.determinate.systems/determinate-nix)
>
> Images are available for these systems:
>
> * `x86++_++64-linux`
> * `aarch64-linux`
> On both systems, the images have these tools installed:
>
> * [Determinate Nix](https://docs.determinate.systems/determinate-nix),
> Determinate Systems' validated and secure
> [Nix](https://docs.determinate.systems/determinate-nix) distribution for
> enterprises. This includes
> https://docs.determinate.systems/determinate-nix#determinate-nixd[Determinate
> Nixd], a utility that enables you to log in to
> [FlakeHub](https://flakehub.com) from AWS using only this command (amongst
> other tasks):
>
> ```shell
> determinate-nixd login token --token-file
> ```
>
> Once logged in, your VM can access
> [FlakeHub Cache](https://docs.determinate.systems/flakehub/cache) and
> [private flakes](https://docs.determinate.systems/flakehub/private-flakes)
> for your organization.
> * [fh](https://docs.determinate.systems/flakehub/cli), the CLI for
> [FlakeHub](https://flakehub.com). You can use fh for things like
> [applying](https://docs.determinate.systems/flakehub/cli#apply-nixos)
> NixOS configurations uploaded to
> [FlakeHub Cache](https://docs.determinate.systems/flakehub/cache). Here’s
> an example:
>
> ```shell
> determinate-nixd login token --token-file
> fh apply nixos "my-org/my-flake/*#nixosConfigurations.my-nixos-configuration-output"
> ```
> Project status:
> **[Static](https://docs.outskirtslabs.com/open-source-vital-signs#static)**.
>
> == Example
>
> For a detailed example of deploying NixOS systems to Hetzner Cloud using
> these images, see our
> [nixos-hetzner-demo](https://github.com/outskirtslabs/nixos-hetzner-demo)
> repo.
>
> Here’s a simple way to get started:
>
> 1. https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/[Generate
> a Hetzner Cloud token]
> 2. Build and upload:
> ```bash
> HCLOUD_TOKEN=...your hcloud token...
> ARCH=x86_64-linux
> HCLOUD_ARCH="x86"
>
> # or
> # ARCH=aarch64-linux
> # HCLOUD_ARCH="arm"
> nix build "github:outskirtslabs/nixos-hetzner#diskImages.$ARCH.hetzner" --print-build-logs
>
> # inspect the image
> ls result/*
> IMAGE_PATH=$(ls result/*.img 2>/dev/null | head -1)
>
> # upload to hetzner cloud
> hcloud-upload-image upload \
> --image-path="$IMAGE_PATH" \
> --architecture="$HCLOUD_ARCH" \
> --description="nixos-hetzner image"
> ```
>
> == Documentation
>
> * [Docs](https://docs.outskirtslabs.com/nixos-hetzner/next/)
> * [API Reference](https://docs.outskirtslabs.com/nixos-hetzner/next/api)
> * https://github.com/outskirtslabs/nixos-hetzner/issues[Support via
> GitHub Issues]
> == Changelog
>
> -++>++ [CHANGELOG.adoc](./CHANGELOG.adoc)
>
> == License: Apache License 2.0
>
> Copyright © 2025 Casey Link casey@outskirtslabs.com
>
> Distributed under the
> [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html).
## ol.clave.acme.account
# ol.clave.acme.account
## validate-account
```clojure
(validate-account account)
```
Validate and normalize an account map, returning the normalized map or throwing ex-info.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L7-L10)
---
## get-primary-contact
```clojure
(get-primary-contact account)
```
Return the primary contact email (without scheme) or nil.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L12-L15)
---
## account-from-edn
```clojure
(account-from-edn registration-edn)
```
Parse an EDN string representing account registration metadata.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L17-L20)
---
## serialize
```clojure
(serialize account keypair)
```
Serialize an account map and keypair into a pretty-printed EDN artifact.
`keypair` is a `java.security.KeyPair`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L22-L27)
---
## deserialize
```clojure
(deserialize account-edn)
```
Deserialize an EDN artifact into [account keypair].
Returns a vector of [account keypair] where keypair is a `java.security.KeyPair`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L29-L34)
---
## generate-keypair
```clojure
(generate-keypair)
(generate-keypair opts)
```
Generate a new ACME account keypair.
Options map:
| | | |
| --- | --- | --- |
| key | description | default |
| :algo | key algorithm (:p256, :p384, :ed25519) | :p256 |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L36-L45)
---
## create
```clojure
(create contact tos-agreed)
(create contact tos-agreed opts)
```
Construct an ACME account map suitable for directory interactions.
`contact` may be a single string or any sequential collection of strings; all
values must be `mailto:` URLs per RFC 8555 Section 7.3.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/account.clj#L47-L55)
## ol.clave.acme.challenge
# ol.clave.acme.challenge
Helpers for working with ACME challenges and authorizations.
## key-authorization
```clojure
(key-authorization challenge account-key)
```
Return the key authorization for `challenge` and `account-key`.
`challenge` may be a map with `::ol.clave.specs/token` or a raw token string.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L11-L19)
---
## dns01-key-authorization
```clojure
(dns01-key-authorization key-authorization)
(dns01-key-authorization challenge account-key)
```
Return the DNS-01 key authorization digest.
When called with a `challenge` map and `account-key`, computes the
key authorization first.
| | |
| --- | --- |
| arity | description |
| `[key-authorization]` | digest the provided key authorization string |
| `[challenge account-key]` | compute key authorization then digest |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L21-L35)
---
## http01-resource-path
```clojure
(http01-resource-path challenge)
```
Return the HTTP-01 resource path for `challenge` or `token`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L37-L43)
---
## dns01-txt-name
```clojure
(dns01-txt-name domain-or-authorization)
```
Return the DNS-01 TXT record name for `domain` or `authorization`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L45-L51)
---
## wildcard?
```clojure
(wildcard? authorization)
```
Return true when the authorization declares a wildcard identifier.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L53-L56)
---
## identifier
```clojure
(identifier authorization)
```
Return the identifier value from an authorization map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L58-L61)
---
## identifier-domain
```clojure
(identifier-domain authorization)
```
Return the identifier domain with any wildcard prefix removed.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L63-L69)
---
## token
```clojure
(token challenge)
```
Return the challenge token string.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L71-L74)
---
## find-by-type
```clojure
(find-by-type authorization type)
```
Return the first challenge in `authorization` matching `type`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L76-L79)
---
## acme-tls-1-protocol
ALPN protocol identifier for TLS-ALPN-01 challenges.
Use this value to detect ACME challenge handshakes in your TLS server’s
ALPN negotiation callback.
See RFC 8737 Section 6.2.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L81-L87)
---
## tlsalpn01-challenge-cert
```clojure
(tlsalpn01-challenge-cert identifier key-authorization)
(tlsalpn01-challenge-cert authorization challenge account-key)
```
Build a TLS-ALPN-01 challenge certificate.
This function has two arities:
Low-level arity `[identifier key-authorization]`:
- `identifier` - Map with `:type` ("dns" or "ip") and `:value`
- `key-authorization` - The computed key authorization string
Convenience arity `[authorization challenge account-key]`:
- `authorization` - Authorization map with `::acme/identifier`
- `challenge` - Challenge map with `::acme/token`
- `account-key` - Account keypair for computing key authorization
Returns a map with:
| | |
| --- | --- |
| key | description |
| `:certificate-der` | DER-encoded certificate bytes |
| `:certificate-pem` | PEM-encoded certificate string |
| `:private-key-der` | DER-encoded private key bytes (PKCS#8) |
| `:private-key-pem` | PEM-encoded private key string (PKCS#8) |
| `:x509` | Parsed `java.security.cert.X509Certificate` |
| `:keypair` | The generated `java.security.KeyPair` |
| `:identifier-type` | The identifier type from input |
| `:identifier-value` | The identifier value from input |
The certificate contains:
- Subject and Issuer: CN=ACME challenge
- SubjectAltName with the identifier (DNS name or IP address)
- Critical acmeValidationV1 extension (OID 1.3.6.1.5.5.7.1.31) containing
the SHA-256 digest of the key authorization
```clojure
;; Low-level usage with pre-computed key authorization
(tlsalpn01-challenge-cert {:type "dns" :value "example.com"}
"token.thumbprint")
;; Convenience usage with authorization and challenge maps
(tlsalpn01-challenge-cert authorization challenge account-key)
```
See RFC 8737 for TLS-ALPN-01 challenge specification.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/challenge.clj#L89-L138)
## ol.clave.acme.commands
# ol.clave.acme.commands
Plumbing layer ACME command API for interacting with an ACME server.
Every command takes an immutable ACME session map as its first argument and
returns a tuple where the first element is the updated session (with refreshed
nonces, account metadata, etc.). This keeps side effects explicit for callers.
Use this namespace when you need precise control over ACME interactions:
Session management:
- [`new-session`](#new-session), [`create-session`](#create-session), [`load-directory`](#load-directory), [`set-polling`](#set-polling)
Account operations:
- [`new-account`](#new-account), [`get-account`](#get-account), [`update-account-contact`](#update-account-contact)
- [`deactivate-account`](#deactivate-account), [`rollover-account-key`](#rollover-account-key)
- [`compute-eab-binding`](#compute-eab-binding)
Order lifecycle:
- [`new-order`](#new-order), [`get-order`](#get-order), [`poll-order`](#poll-order), [`finalize-order`](#finalize-order)
Authorization and challenges:
- [`get-authorization`](#get-authorization), [`poll-authorization`](#poll-authorization), [`deactivate-authorization`](#deactivate-authorization)
- [`respond-challenge`](#respond-challenge)
Certificate operations:
- [`get-certificate`](#get-certificate), [`revoke-certificate`](#revoke-certificate)
Renewal information (ARI per RFC 9773):
- [`get-renewal-info`](#get-renewal-info)
Terms of Service:
- [`check-terms-of-service`](#check-terms-of-service)
Pair these commands with `ol.clave.scope` to enforce timeouts and cancellation
across long-running workflows such as account setup or certificate issuance.
## new-session
```clojure
(new-session directory-url)
(new-session directory-url opts)
```
Create an ACME session value without issuing network requests.
Parameters:
- `directory-url` — ACME directory URL as a string.
- `opts` — optional map configuring the session. Recognised keys are
summarised below.
| | | |
| --- | --- | --- |
| Key | Type | Description |
| `:http-client` | map | Passed to `ol.clave.impl.http/http-client` to build the transport layer. |
| `:account-key` | `java.security.KeyPair` | Injects an existing key pair into the session. |
| `:account-kid` | string | Stores a known account URL for authenticated calls. |
| `:scope` | scope token | Overrides the default cancellation/timeout scope. |
Returns `[session nil]`, where `session` is a qualified map of
`::ol.clave.specs/*` keys suitable for subsequent commands.
Example:
```clojure
(require '[ol.clave.commands :as commands])
(let [[session _] (commands/new-session "https://acme.example/dir" {:http-client {}})]
(::ol.clave.specs/directory-url session))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L39-L67)
---
## load-directory
```clojure
(load-directory lease session)
(load-directory lease session opts)
```
Fetch the ACME directory document and attach it to `session`.
Uses a global cache with 12-hour TTL to avoid repeated fetches for
long-running servers managing multiple domains.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — session created by [`new-session`](#new-session) or [`create-session`](#create-session).
- `opts` — optional map with overrides.
Options:
| | |
| --- | --- |
| key | description |
| `:force` | Bypass cache, fetch fresh from CA. |
| `:ttl-ms` | Custom cache TTL in milliseconds. |
Returns `[updated-session directory]`, where `directory` is the qualified map
described by `::ol.clave.specs/directory` and `updated-session` has the
directory attached.
When loaded from cache, the session will not have a nonce from this call;
the first JWS operation will fetch one via HEAD to newNonce.
Example:
```clojure
(require '[ol.clave.commands :as commands]
'[ol.clave.lease :as lease])
(let [bg (lease/background)
[session _] (commands/new-session "https://acme.example/dir" {:http-client {}})]
(commands/load-directory bg session))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L69-L106)
---
## set-polling
```clojure
(set-polling session opts)
```
Update default polling parameters in the session.
Parameters:
- `session` — ACME session map.
- `opts` — map with optional polling configuration keys.
Options:
| | |
| --- | --- |
| key | description |
| `:interval-ms` | Default poll interval fallback (ms). |
| `:timeout-ms` | Default overall poll timeout (ms). |
Returns the updated session with new polling defaults.
Example:
```clojure
(-> session
(commands/set-polling {:interval-ms 2000 :timeout-ms 120000}))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L108-L130)
---
## create-session
```clojure
(create-session lease directory-url)
(create-session lease directory-url opts)
```
Build a session and eagerly download the ACME directory.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `directory-url` — ACME directory URL.
- `opts` — same options map accepted by [`new-session`](#new-session), optionally extended
with `:force` to bypass the directory cache, and `:ttl-ms` for custom cache TTL.
Returns `[session directory]` with the directory hydrated and incorporated
into `session`.
Example:
```clojure
(require '[ol.clave.commands :as commands]
'[ol.clave.lease :as lease])
(commands/create-session (lease/background) "https://acme.example/dir" {:http-client {}})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L132-L154)
---
## compute-eab-binding
```clojure
(compute-eab-binding eab-opts account-key endpoint)
```
Produce an External Account Binding structure for account creation.
Parameters:
- `eab-opts` — map with `:kid` and `:mac-key`, or `nil` to skip the binding.
- `account-key` — existing account key pair (`java.security.KeyPair`).
- `endpoint` — directory key identifying the `newAccount` URL.
Returns the binding map described by RFC 8555 §7.3.4 or `nil` when
`eab-opts` is missing. Invalid base64 encoding raises
`::ol.clave.errors/invalid-eab`.
Example:
```clojure
(require '[ol.clave.commands :as commands])
(commands/compute-eab-binding {:kid "kid-123" :mac-key "q83l..."}
account-key
"https://acme.example/new-account")
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L156-L177)
---
## new-account
```clojure
(new-account lease session account)
(new-account lease session account opts)
```
Register a new ACME account at the directory’s `newAccount` endpoint.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated or unauthenticated ACME session map.
- `account` — account data using `::ol.clave.specs/*` keys.
- `opts` — optional map supporting the keys listed below.
| | |
| --- | --- |
| Key | Description |
| `:external-account` | `{:kid string :mac-key bytes-or-base64}` enabling External Account Binding. |
Returns `[updated-session normalized-account]`. The session gains the account
KID and the response account is merged onto the supplied `account` map.
Example:
```clojure
(require '[ol.clave.commands :as commands]
'[ol.clave.account :as account]
'[ol.clave.lease :as lease])
(let [[acct key] (account/deserialize (slurp "test/fixtures/test-account.edn"))]
(commands/new-account (lease/background) session acct))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L179-L207)
---
## find-account-by-key
```clojure
(find-account-by-key lease session)
(find-account-by-key lease session opts)
```
Look up an existing ACME account by its public key.
Uses the newAccount endpoint with `onlyReturnExisting: true` to find an
account without creating one. This is useful for key recovery scenarios
where you have the account key but lost the account URL.
Parameters:
- `lease` - A lease for cooperative cancellation.
- `session` - Session with account key set (via `:account-key` option).
Returns `[updated-session account-kid]` where `account-kid` is the account
URL string. The session is updated with the account KID.
Throws `::ol.clave.errors/account-not-found` if no account exists for the key.
Throws `::ol.clave.errors/invalid-account-key` if session has no account key.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L209-L228)
---
## get-account
```clojure
(get-account lease session account)
(get-account lease session account opts)
```
Retrieve the current state of an ACME account via POST-as-GET.
Parameters:
- `lease` - A lease for cooperative cancellation.
- `session` — session containing `::ol.clave.specs/account-kid` and
`::ol.clave.specs/account-key`.
- `account` — baseline account map that will be merged with the server
response.
Returns `[updated-session account-map]` where `account-map` is the merged
account including the authoritative contact info and account status.
Example:
```clojure
(require '[ol.clave.commands :as commands])
(commands/get-account lease session account)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L230-L252)
---
## update-account-contact
```clojure
(update-account-contact lease session account contacts)
(update-account-contact lease session account contacts opts)
```
Replace the contact URIs registered for an ACME account.
Parameters:
- `lease` - A lease for cooperative cancellation.
- `session` — authenticated session.
- `account` — current account map.
- `contacts` — vector of `mailto:` URIs to set on the account.
Returns `[updated-session updated-account]` with contacts normalised to a
vector of strings sourced from the server response.
Example:
```clojure
(require '[ol.clave.commands :as commands])
(commands/update-account-contact lease session account ["mailto:admin@example.com"])
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L254-L275)
---
## deactivate-account
```clojure
(deactivate-account lease session account)
(deactivate-account lease session account opts)
```
Deactivate an ACME account by issuing a status change request.
Parameters:
- `lease` - A lease for cooperative cancellation.
- `session` — authenticated session.
- `account` — account map with identifying information.
Returns `[updated-session deactivated-account]`. Subsequent account
operations will fail with `::ol.clave.errors/unauthorized-account`.
Example:
```clojure
(require '[ol.clave.commands :as commands])
(commands/deactivate-account lease session account)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L277-L297)
---
## rollover-account-key
```clojure
(rollover-account-key lease session account new-account-key)
(rollover-account-key lease session account new-account-key opts)
```
Replace the account key pair using the directory `keyChange` endpoint.
Parameters:
- `lease` - A lease for cooperative cancellation.
- `session` — authenticated session containing the current account key and KID.
- `account` — account data used to verify the new key.
- `new-account-key` — `java.security.KeyPair` to install.
Returns `[updated-session verified-account]` with the session updated to store
`new-account-key`. Verification failures raise
`::ol.clave.errors/account-key-rollover-verification-failed`.
Example:
```clojure
(require '[ol.clave.commands :as commands]
'[ol.clave.account :as account])
(let [new-key (account/generate-keypair)]
(commands/rollover-account-key lease session account new-key))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L299-L323)
---
## new-order
```clojure
(new-order lease session order)
(new-order lease session order opts)
```
Create a new ACME order for the supplied identifiers.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session with account key and KID.
- `order` — map containing `::ol.clave.specs/identifiers` and optional
`::ol.clave.specs/notBefore` / `::ol.clave.specs/notAfter`.
- `opts` — optional map with overrides.
Options:
| | |
| --- | --- |
| key | description |
| `:profile` | Optional profile name as a string when the directory advertises `:profiles`. |
Returns `[updated-session order]` where `order` is the normalized order map
including `::ol.clave.specs/order-location`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L325-L346)
---
## get-order
```clojure
(get-order lease session order-url)
(get-order lease session order-url opts)
```
Retrieve the current state of an order via POST-as-GET.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `order-url` — order URL string, or an order map that includes
`::ol.clave.specs/order-location`.
- `opts` — optional map with overrides.
Returns `[updated-session order]` with the latest order data.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L348-L362)
---
## poll-order
```clojure
(poll-order lease session order-url)
```
Poll an order URL until it reaches a terminal status.
Timeout is the lesser of session’s poll-timeout and the lease’s deadline.
Configure polling defaults via [`set-polling`](#set-polling).
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `order-url` — order URL string.
Returns `[updated-session order]` on success or throws on invalid/timeout.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L364-L377)
---
## finalize-order
```clojure
(finalize-order lease session order csr)
(finalize-order lease session order csr opts)
```
Finalize an order by submitting a CSR.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `order` — normalized order map with `::ol.clave.specs/status` and
`::ol.clave.specs/finalize`.
- `csr` — map containing `:csr-b64url` from [`ol.clave.certificate.impl.csr/create-csr`](api/ol-clave-certificate-impl-csr.adoc#create-csr).
- `opts` — optional map with overrides.
Returns `[updated-session order]` with the updated order state.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L379-L394)
---
## get-certificate
```clojure
(get-certificate lease session certificate-url)
(get-certificate lease session certificate-url opts)
```
Download a PEM certificate chain from the certificate URL via POST-as-GET.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session carrying HTTP configuration.
- `certificate-url` — certificate URL from an order.
- `opts` — optional map with overrides.
Returns `[updated-session result]` where `result` includes `:chains` and
`:preferred` entries with parsed PEM data.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L396-L410)
---
## get-authorization
```clojure
(get-authorization lease session authorization-url)
(get-authorization lease session authorization-url opts)
```
Fetch an authorization resource via POST-as-GET.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `authorization-url` — authorization URL string, or an authorization map
containing `::ol.clave.specs/authorization-location`.
- `opts` — optional map with overrides.
Returns `[updated-session authorization]`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L412-L426)
---
## poll-authorization
```clojure
(poll-authorization lease session authorization-url)
```
Poll an authorization URL until it reaches a terminal status.
Timeout is the lesser of session' poll-timeout and lease’s deadline.
Configure polling defaults via [`set-polling`](#set-polling).
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `authorization-url` — authorization URL string.
Returns `[updated-session authorization]` on success or throws when invalid,
unusable, or timed out.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L428-L442)
---
## deactivate-authorization
```clojure
(deactivate-authorization lease session authorization-url)
(deactivate-authorization lease session authorization-url opts)
```
Deactivate an authorization by sending a status update.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `authorization-url` — authorization URL string, or authorization map.
- `opts` — optional map with overrides.
Returns `[updated-session authorization]`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L444-L457)
---
## new-authorization
```clojure
(new-authorization lease session identifier)
(new-authorization lease session identifier opts)
```
Create a pre-authorization for an identifier via the newAuthz endpoint.
Pre-authorization (RFC 8555 Section 7.4.1) allows clients to obtain
authorization proactively, outside the context of a specific order.
This is useful for hosting providers who want to authorize domains
before virtual servers are created.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `identifier` — map with `:type` and `:value` keys.
- `opts` — optional map with overrides.
Pre-authorization cannot be used with wildcard identifiers.
Not all ACME servers support this endpoint.
Returns `[updated-session authorization]`.
Throws:
- `::ol.clave.errors/pre-authorization-unsupported` if server does not
advertise newAuthz endpoint.
- `::ol.clave.errors/wildcard-identifier-not-allowed` if identifier is
a wildcard.
- `::ol.clave.errors/pre-authorization-failed` if server rejects the request.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L459-L487)
---
## respond-challenge
```clojure
(respond-challenge lease session challenge)
(respond-challenge lease session challenge opts)
```
Notify the ACME server that a challenge response is ready.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session.
- `challenge` — challenge map containing `::ol.clave.specs/url`.
- `opts` — optional map with overrides.
Options:
| | |
| --- | --- |
| key | description |
| `:payload` | Override the default `{}` payload. |
Returns `[updated-session challenge]`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L489-L508)
---
## revoke-certificate
```clojure
(revoke-certificate lease session certificate)
(revoke-certificate lease session certificate opts)
```
Revoke a certificate via the directory’s revokeCert endpoint.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` — authenticated session or session with directory loaded.
- `certificate` — `java.security.cert.X509Certificate` or DER bytes.
- `opts` — optional map with overrides.
Options:
| | |
| --- | --- |
| key | description |
| `:reason` | RFC 5280 reason code integer (0-6, 8-10). |
| `:signing-key` | `java.security.KeyPair` for certificate-key authorization. |
When `:signing-key` is provided, uses certificate-key authorization with
JWK-embedded JWS. Otherwise uses account-key authorization requiring an
authenticated session.
Returns `[updated-session nil]` on success.
Example:
```clojure
(commands/revoke-certificate lease session cert {:reason 1})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L510-L539)
---
## get-renewal-info
```clojure
(get-renewal-info lease session cert-or-id)
(get-renewal-info lease session cert-or-id opts)
```
Fetch ACME Renewal Information (ARI) for a certificate per RFC 9773.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` - ACME session with directory loaded.
- `cert-or-id` - X509Certificate or precomputed renewal identifier string.
- `opts` - optional map with overrides.
When `cert-or-id` is a certificate, the renewal identifier is derived from
the Authority Key Identifier extension and serial number.
Returns `[updated-session renewal-info]` where `renewal-info` contains
`:suggested-window` (map with `:start` and `:end` instants),
`:retry-after-ms`, and optional `:explanation-url`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L541-L559)
---
## check-terms-of-service
```clojure
(check-terms-of-service lease session)
(check-terms-of-service lease session opts)
```
Check for Terms of Service changes by comparing directory meta values.
Parameters:
- `lease` — lease for cancellation/timeout control.
- `session` - ACME session with directory already loaded.
- `opts` - optional map with overrides.
Refreshes the directory from the server and compares the `termsOfService`
field in the meta section with the previously loaded value.
Returns `[updated-session tos-change]` where `tos-change` contains:
- `:changed?` - true if termsOfService URL changed
- `:previous` - previous termsOfService URL or nil
- `:current` - current termsOfService URL or nil
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/commands.clj#L561-L579)
## ol.clave.acme.impl.account
# ol.clave.acme.impl.account
## validate-account
```clojure
(validate-account account)
```
See [`ol.clave.acme.account/validate-account`](api/ol-clave-acme-account.adoc#validate-account)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L43-L64)
---
## get-primary-contact
```clojure
(get-primary-contact account)
```
See [`ol.clave.acme.account/get-primary-contact`](api/ol-clave-acme-account.adoc#get-primary-contact)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L66-L71)
---
## account-from-edn
```clojure
(account-from-edn registration-edn)
```
See [`ol.clave.acme.account/account-from-edn`](api/ol-clave-acme-account.adoc#account-from-edn)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L73-L84)
---
## serialize
```clojure
(serialize account keypair)
```
See [`ol.clave.acme.account/serialize`](api/ol-clave-acme-account.adoc#serialize)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L93-L105)
---
## deserialize
```clojure
(deserialize account-edn)
```
See [`ol.clave.acme.account/deserialize`](api/ol-clave-acme-account.adoc#deserialize)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L107-L124)
---
## generate-keypair
```clojure
(generate-keypair)
(generate-keypair {:keys [algo] :or {algo :p256}})
```
See [`ol.clave.acme.account/generate-keypair`](api/ol-clave-acme-account.adoc#generate-keypair)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L126-L131)
---
## create
```clojure
(create contact tos-agreed)
(create contact tos-agreed _)
```
See [`ol.clave.acme.account/create`](api/ol-clave-acme-account.adoc#create)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/account.clj#L133-L157)
## ol.clave.acme.impl.ari
# ol.clave.acme.impl.ari
ARI identifier derivation helpers per RFC 9773.
Extracts the Authority Key Identifier keyIdentifier and serial number
from an X509Certificate and builds the unique renewal identifier string.
## authority-key-identifier
```clojure
(authority-key-identifier cert)
```
Extract the keyIdentifier bytes from the AKI extension of a certificate.
Parameters:
- `cert` - X509Certificate to extract AKI from.
Returns the keyIdentifier bytes or throws `::errors/renewal-info-invalid`
if the AKI extension is missing or does not contain a keyIdentifier.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/ari.clj#L23-L57)
---
## serial-der-bytes
```clojure
(serial-der-bytes cert)
```
Return the DER-encoded serial number bytes of a certificate.
Per RFC 9773, this is the two’s complement encoding of the serial number
with a leading zero byte if the high bit is set (to preserve positive sign).
Parameters:
- `cert` - X509Certificate to extract serial from.
Returns the DER-encoded serial number bytes (without tag and length).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/ari.clj#L59-L73)
---
## renewal-id
```clojure
(renewal-id cert)
```
Derive the ARI renewal identifier from a certificate.
The identifier is: base64url(AKI keyIdentifier) || '.' || base64url(serial DER)
with all trailing padding ('=') stripped per RFC 9773.
Parameters:
- `cert` - X509Certificate to derive identifier from.
Returns the renewal identifier string.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/ari.clj#L75-L90)
---
## normalize-renewal-info
```clojure
(normalize-renewal-info body retry-after-ms)
```
Normalize a RenewalInfo response from the server.
Parameters:
- `body` - parsed JSON response body as a map (keyword or string keys).
- `retry-after-ms` - Retry-After value in milliseconds.
Returns a normalized map with `:suggested-window`, optional `:explanation-url`,
and `:retry-after-ms`. Throws `::errors/renewal-info-invalid` if the response
is malformed or the window is invalid.
The suggested window must have end strictly after start per RFC 9773.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/ari.clj#L107-L144)
## ol.clave.acme.impl.authorization
# ol.clave.acme.impl.authorization
## normalize-authorization
```clojure
(normalize-authorization authorization account-key location)
```
Normalize an authorization response into qualified keys and computed data.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L12-L27)
---
## authorization-valid?
```clojure
(authorization-valid? authorization)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L29-L31)
---
## authorization-invalid?
```clojure
(authorization-invalid? authorization)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L33-L35)
---
## authorization-unusable?
```clojure
(authorization-unusable? authorization)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L37-L39)
---
## authorization-problem
```clojure
(authorization-problem authorization)
```
Return the most relevant problem map from an authorization.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L41-L45)
---
## wildcard-identifier?
```clojure
(wildcard-identifier? identifier)
```
Return true when `identifier` is a wildcard (value starts with *.).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/authorization.clj#L47-L52)
## ol.clave.acme.impl.challenge
# ol.clave.acme.impl.challenge
## key-authorization
```clojure
(key-authorization token account-keypair)
```
See [`ol.clave.acme.challenge/key-authorization`](api/ol-clave-acme-challenge.adoc#key-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/challenge.clj#L14-L19)
---
## dns01-key-authorization
```clojure
(dns01-key-authorization key-authorization)
```
See [`ol.clave.acme.challenge/dns01-key-authorization`](api/ol-clave-acme-challenge.adoc#dns01-key-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/challenge.clj#L21-L27)
---
## http01-resource-path
```clojure
(http01-resource-path token)
```
See [`ol.clave.acme.challenge/http01-resource-path`](api/ol-clave-acme-challenge.adoc#http01-resource-path)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/challenge.clj#L29-L32)
---
## dns01-txt-name
```clojure
(dns01-txt-name domain)
```
See [`ol.clave.acme.challenge/dns01-txt-name`](api/ol-clave-acme-challenge.adoc#dns01-txt-name)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/challenge.clj#L34-L38)
---
## normalize-challenge
```clojure
(normalize-challenge challenge)
(normalize-challenge challenge account-key)
```
Qualify a challenge map and attach computed key authorization when possible.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/challenge.clj#L40-L50)
## ol.clave.acme.impl.commands
# ol.clave.acme.impl.commands
## default-poll-interval-ms
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L28-L28)
---
## default-poll-timeout-ms
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L29-L29)
---
## new-session
```clojure
(new-session directory-url {:keys [http-client account-key account-kid]})
```
See [`ol.clave.acme.commands/new-session`](api/ol-clave-acme-commands.adoc#new-session)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L31-L43)
---
## set-polling
```clojure
(set-polling session {:keys [interval-ms timeout-ms]})
```
See [`ol.clave.acme.commands/set-polling`](api/ol-clave-acme-commands.adoc#set-polling)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L45-L50)
---
## load-directory
```clojure
(load-directory lease session)
(load-directory lease {::acme/keys [directory-url] :as session} opts)
```
See [`ol.clave.acme.commands/load-directory`](api/ol-clave-acme-commands.adoc#load-directory)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L52-L80)
---
## create-session
```clojure
(create-session lease directory-url opts)
```
See [`ol.clave.acme.commands/create-session`](api/ol-clave-acme-commands.adoc#create-session)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L82-L87)
---
## compute-eab-binding
```clojure
(compute-eab-binding eab-opts account-key endpoint)
```
See [`ol.clave.acme.commands/compute-eab-binding`](api/ol-clave-acme-commands.adoc#compute-eab-binding)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L89-L104)
---
## new-account
```clojure
(new-account lease session account)
(new-account lease session account opts)
```
See [`ol.clave.acme.commands/new-account`](api/ol-clave-acme-commands.adoc#new-account)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L106-L145)
---
## find-account-by-key
```clojure
(find-account-by-key lease session)
(find-account-by-key lease session _opts)
```
See [`ol.clave.acme.commands/find-account-by-key`](api/ol-clave-acme-commands.adoc#find-account-by-key)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L147-L181)
---
## get-account
```clojure
(get-account lease session account)
(get-account lease session account _opts)
```
See [`ol.clave.acme.commands/get-account`](api/ol-clave-acme-commands.adoc#get-account)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L242-L252)
---
## update-account-contact
```clojure
(update-account-contact lease session account contacts)
(update-account-contact lease session account contacts _opts)
```
See [`ol.clave.acme.commands/update-account-contact`](api/ol-clave-acme-commands.adoc#update-account-contact)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L254-L270)
---
## deactivate-account
```clojure
(deactivate-account lease session account)
(deactivate-account lease session account _opts)
```
See [`ol.clave.acme.commands/deactivate-account`](api/ol-clave-acme-commands.adoc#deactivate-account)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L272-L281)
---
## rollover-account-key
```clojure
(rollover-account-key lease session account new-account-key)
(rollover-account-key lease session account new-account-key _opts)
```
See [`ol.clave.acme.commands/rollover-account-key`](api/ol-clave-acme-commands.adoc#rollover-account-key)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L283-L316)
---
## new-order
```clojure
(new-order lease session order)
(new-order lease session order opts)
```
See [`ol.clave.acme.commands/new-order`](api/ol-clave-acme-commands.adoc#new-order)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L318-L351)
---
## get-order
```clojure
(get-order lease session order-or-url)
(get-order lease session order-or-url _opts)
```
See [`ol.clave.acme.commands/get-order`](api/ol-clave-acme-commands.adoc#get-order)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L371-L383)
---
## poll-order
```clojure
(poll-order lease session order-url)
```
See [`ol.clave.acme.commands/poll-order`](api/ol-clave-acme-commands.adoc#poll-order)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L385-L432)
---
## finalize-order
```clojure
(finalize-order lease session order csr)
(finalize-order lease session order csr _opts)
```
See [`ol.clave.acme.commands/finalize-order`](api/ol-clave-acme-commands.adoc#finalize-order)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L434-L479)
---
## get-authorization
```clojure
(get-authorization lease session authorization-or-url)
(get-authorization lease session authorization-or-url _opts)
```
See [`ol.clave.acme.commands/get-authorization`](api/ol-clave-acme-commands.adoc#get-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L499-L509)
---
## poll-authorization
```clojure
(poll-authorization lease session authorization-url)
```
See [`ol.clave.acme.commands/poll-authorization`](api/ol-clave-acme-commands.adoc#poll-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L511-L566)
---
## deactivate-authorization
```clojure
(deactivate-authorization lease session authorization-or-url)
(deactivate-authorization lease session authorization-or-url _opts)
```
See [`ol.clave.acme.commands/deactivate-authorization`](api/ol-clave-acme-commands.adoc#deactivate-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L568-L591)
---
## new-authorization
```clojure
(new-authorization lease session identifier)
(new-authorization lease session identifier _opts)
```
See [`ol.clave.acme.commands/new-authorization`](api/ol-clave-acme-commands.adoc#new-authorization)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L593-L636)
---
## respond-challenge
```clojure
(respond-challenge lease session challenge)
(respond-challenge lease session challenge opts)
```
See [`ol.clave.acme.commands/respond-challenge`](api/ol-clave-acme-commands.adoc#respond-challenge)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L638-L659)
---
## get-certificate
```clojure
(get-certificate lease session certificate-url)
(get-certificate lease session certificate-url _opts)
```
See [`ol.clave.acme.commands/get-certificate`](api/ol-clave-acme-commands.adoc#get-certificate)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L661-L689)
---
## revoke-certificate
```clojure
(revoke-certificate lease session certificate)
(revoke-certificate lease session certificate opts)
```
See [`ol.clave.acme.commands/revoke-certificate`](api/ol-clave-acme-commands.adoc#revoke-certificate)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L691-L738)
---
## get-renewal-info
```clojure
(get-renewal-info lease session cert-or-id)
(get-renewal-info lease session cert-or-id _opts)
```
See [`ol.clave.acme.commands/get-renewal-info`](api/ol-clave-acme-commands.adoc#get-renewal-info)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L755-L792)
---
## check-terms-of-service
```clojure
(check-terms-of-service lease session)
(check-terms-of-service lease session _opts)
```
See [`ol.clave.acme.commands/check-terms-of-service`](api/ol-clave-acme-commands.adoc#check-terms-of-service)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/commands.clj#L794-L803)
## ol.clave.acme.impl.directory-cache
# ol.clave.acme.impl.directory-cache
Global directory cache with TTL for ACME directory responses.
ACME directories rarely change, so caching them avoids unnecessary network
requests for long-running servers managing many domains.
The cache is keyed by directory URL with a 12-hour default TTL.
Stale entries remain until replaced (no background cleanup).
## default-ttl-ms
Default TTL of 12 hours in milliseconds.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/directory_cache.clj#L10-L12)
---
## cache-get
```clojure
(cache-get url)
(cache-get url ttl-ms)
```
Returns cached directory for url if present and fresh, else nil.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/directory_cache.clj#L25-L31)
---
## cache-put
```clojure
(cache-put url directory)
```
Stores directory in cache with current timestamp. Returns directory.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/directory_cache.clj#L33-L38)
---
## cache-clear
```clojure
(cache-clear)
```
Clears entire directory cache. Useful for testing.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/directory_cache.clj#L40-L43)
---
## cache-evict
```clojure
(cache-evict url)
```
Removes single entry from cache.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/directory_cache.clj#L45-L48)
## ol.clave.acme.impl.http.interceptors
# ol.clave.acme.impl.http.interceptors
## ->uri
```clojure
(->uri uri)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L20-L30)
---
## accept-header
Request: adds `:accept` header. Only supported value is `:json`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L54-L66)
---
## uri-with-query
```clojure
(uri-with-query uri new-query)
```
We can’t use the URI constructor because it encodes all arguments for us.
See https://stackoverflow.com/a/77971448/6264
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L84-L98)
---
## query-params
Request: encodes `:query-params` map and appends to `:uri`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L100-L109)
---
## form-params
Request: encodes `:form-params` map and adds `:body`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L122-L132)
---
## decompress-body
Response: decompresses body based on "content-encoding" header. Valid values: `gzip` and `deflate`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L176-L183)
---
## decode-body
Response: based on the value of `:as` in request, decodes as `:string`, `:stream` or `:bytes`. Defaults to `:string`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L190-L202)
---
## construct-uri
Request: construct uri from map
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L204-L211)
---
## request-method
Request: normalize :method option
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L213-L220)
---
## unexceptional-statuses
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L222-L223)
---
## throw-on-exceptional-status-code
Response: throw on exceptional status codes
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L225-L234)
---
## parse-json-body
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L236-L250)
---
## default-interceptors
Default interceptor chain. Interceptors are called in order for request and in reverse order for response.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/http/interceptors.clj#L252-L262)
## ol.clave.acme.impl.order
# ol.clave.acme.impl.order
## create-identifier
```clojure
(create-identifier identifier)
(create-identifier identifier-type identifier-value)
```
See [`ol.clave.acme.order/create-identifier`](api/ol-clave-acme-order.adoc#create-identifier)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L21-L37)
---
## create
```clojure
(create identifiers)
(create identifiers opts)
```
See [`ol.clave.acme.order/create`](api/ol-clave-acme-order.adoc#create)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L39-L62)
---
## build-order-payload
```clojure
(build-order-payload order)
```
Build an ACME newOrder payload from a qualified order map.
Parameters:
- `order` - Order map with `::acme/identifiers` and optional
`::acme/notBefore`, `::acme/notAfter`, `::acme/replaces`.
The `replaces` field (RFC 9773) links a renewal order to its predecessor
certificate using the ARI unique identifier format.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L74-L101)
---
## normalize-order
```clojure
(normalize-order order location)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L103-L113)
---
## ensure-identifiers-consistent
```clojure
(ensure-identifiers-consistent expected order)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L115-L122)
---
## order-ready?
```clojure
(order-ready? order)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L124-L126)
---
## order-terminal?
```clojure
(order-terminal? order)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/order.clj#L128-L130)
## ol.clave.acme.impl.revocation
# ol.clave.acme.impl.revocation
Pure helpers for certificate revocation payload construction and validation.
This namespace handles:
- Extracting DER bytes from X509Certificate or raw bytes
- Constructing revocation payloads with base64url-encoded certificates
- Validating RFC 5280 reason codes for ACME revocation
## valid-reason?
```clojure
(valid-reason? reason)
```
Return true if `reason` is a valid RFC 5280 revocation reason code for ACME.
Valid codes are 0-6 and 8-10. Code 7 is unused in RFC 5280.
Returns false for non-integer values.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/revocation.clj#L21-L28)
---
## certificate->der
```clojure
(certificate->der certificate)
```
Extract DER-encoded bytes from a certificate.
Accepts either:
- `java.security.cert.X509Certificate` - extracts via `.getEncoded()`
- `byte[]` - returns as-is
Returns the DER-encoded certificate bytes.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/revocation.clj#L30-L49)
---
## payload
```clojure
(payload certificate)
(payload certificate opts)
```
Construct a revocation request payload.
Parameters:
- `certificate` - `X509Certificate` or DER bytes
- `opts` - optional map with `:reason` (RFC 5280 reason code)
Returns a map with:
- `:certificate` - base64url-encoded DER
- `:reason` - reason code (when provided in opts)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/revocation.clj#L51-L67)
## ol.clave.acme.impl.tos
# ol.clave.acme.impl.tos
Terms of Service change detection helpers.
Compares prior and current directory meta values to detect ToS changes.
## compare-terms
```clojure
(compare-terms previous current)
```
Compare previous and current directory meta maps for ToS changes.
Parameters:
- `previous` - previous directory meta map with `::specs/termsOfService`.
- `current` - current directory meta map with `::specs/termsOfService`.
Returns a map with:
- `:changed?` - true if termsOfService values differ
- `:previous` - previous termsOfService URL or nil
- `:current` - current termsOfService URL or nil
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/impl/tos.clj#L10-L26)
## ol.clave.acme.order
# ol.clave.acme.order
Helpers for building and inspecting ACME orders.
## create-identifier
```clojure
(create-identifier identifier)
(create-identifier identifier-type identifier-value)
```
Construct an identifier map from `identifier` or `type` and `value`.
`type` may be a string or keyword such as `dns` or `:dns`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L9-L16)
---
## create
```clojure
(create identifiers)
(create identifiers opts)
```
Construct an order map with the supplied identifiers and options.
Options:
| | |
| --- | --- |
| key | description |
| `:not-before` | Optional notBefore instant or RFC3339 string. |
| `:not-after` | Optional notAfter instant or RFC3339 string. |
| `:profile` | Optional profile name as a string. |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L18-L32)
---
## identifiers
```clojure
(identifiers order)
```
Return the identifiers on an order map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L34-L37)
---
## authorizations
```clojure
(authorizations order)
```
Return the authorization URLs from an order map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L39-L42)
---
## url
```clojure
(url order)
```
Return the order URL from an order map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L44-L47)
---
## certificate-url
```clojure
(certificate-url order)
```
Return the certificate URL from an order map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/order.clj#L49-L52)
## ol.clave.acme.solver.http
# ol.clave.acme.solver.http
HTTP-01 challenge solver with Ring middleware.
This namespace provides a complete HTTP-01 challenge solution for clojure ring servers.
* [`solver`](#solver) creates a solver map for use with `ol.clave/obtain-certificate`
* [`wrap-acme-challenge`](#wrap-acme-challenge) is Ring middleware that serves challenge responses
Usage:
```clojure
(require '[ol.clave.acme.solver.http :as http-solver])
(def my-solver (http-solver/solver))
;; Add middleware to your Ring app
(def app (-> your-handler
(http-solver/wrap-acme-challenge my-solver)))
;; Use solver with obtain-certificate
(clave/obtain-certificate lease session identifiers cert-key
{:http-01 my-solver} {})
```
## solver
```clojure
(solver)
```
Create an HTTP-01 solver that registers challenges in a registry.
Creates its own registry atom internally.
The [`wrap-acme-challenge`](#wrap-acme-challenge) middleware reads from this registry to serve
challenge responses.
Returns a solver map with `:present`, `:cleanup`, and `:registry`.
Example:
```clojure
(def my-solver (solver))
;; Pass solver to middleware
(def app (-> handler (wrap-acme-challenge my-solver)))
;; Use with obtain-certificate
(clave/obtain-certificate lease session identifiers cert-key
{:http-01 my-solver} {})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/http.clj#L32-L62)
---
## wrap-acme-challenge
```clojure
(wrap-acme-challenge handler solver)
```
Ring middleware that serves ACME HTTP-01 challenge responses.
Intercepts requests to `/.well-known/acme-challenge/{token}` and returns
the key-authorization from the solver’s registry.
Other requests pass through to the wrapped handler.
Parameters:
| | |
| --- | --- |
| name | description |
| `handler` | The Ring handler to wrap |
| `solver` | Solver created by [`solver`](#solver) |
Response behavior:
- Returns 200 with key-authorization as plain text if token found
- Returns 404 if token not in registry
- Passes through to handler for non-challenge paths
Example:
```clojure
(def my-solver (solver))
(def app
(-> my-handler
(wrap-acme-challenge my-solver)))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/http.clj#L64-L105)
---
## handler
```clojure
(handler solver)
```
Standalone Ring handler for ACME HTTP-01 challenges.
Use this when you want a dedicated server for challenges rather than
integrating with an existing application.
Parameters:
| | |
| --- | --- |
| name | description |
| `solver` | Solver created by [`solver`](#solver) |
Returns a Ring handler function.
Example:
```clojure
(def my-solver (solver))
;; Start a dedicated challenge server
(run-jetty (http-solver/handler my-solver) {:port 80})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/http.clj#L107-L129)
## ol.clave.acme.solver.tls-alpn
# ol.clave.acme.solver.tls-alpn
TLS-ALPN-01 challenge solver.
Provides two solver implementations:
- [`bootstrap-solver`](#bootstrap-solver) - Starts temporary SSLServerSocket (before TLS server running)
- [`integrated-solver`](#integrated-solver) - Registers with KeyManager (when TLS server running)
For typical use, [`switchable-solver`](#switchable-solver) creates a solver that starts in bootstrap
mode and can be switched to integrated mode after your TLS server starts.
Usage:
```clojure
(require '[ol.clave.acme.solver.tls-alpn :as tls-alpn])
(def solver (tls-alpn/switchable-solver {:port 443}))
;; Use solver with automation system
(auto/start {:solvers {:tls-alpn-01 solver} ...})
(auto/manage-domains system ["example.com"])
;; Pass registry to sni-alpn-ssl-context
(jetty-ext/sni-alpn-ssl-context lookup-fn (tls-alpn/challenge-registry solver))
;; After TLS server starts, switch to integrated mode for renewals
(tls-alpn/switch-to-integrated solver)
```
## bootstrap-solver
```clojure
(bootstrap-solver {:keys [port] :or {port 443}})
```
TLS-ALPN-01 solver that starts a temporary server.
Use for initial certificate acquisition before the main TLS server starts.
Starts an SSLServerSocket during `:present`, stops it during `:cleanup`.
Options:
| | | |
| --- | --- | --- |
| key | description | default |
| `:port` | Port for challenge server | 443 |
Returns a solver map with `:present` and `:cleanup` functions.
```clojure
(def solver (bootstrap-solver {:port 8443}))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/tls_alpn.clj#L120-L157)
---
## integrated-solver
```clojure
(integrated-solver)
```
TLS-ALPN-01 solver that registers with an existing TLS server.
Use for certificate renewals when the main TLS server is running.
Registers challenge cert data in a registry for the server’s
KeyManager to serve during ALPN handshakes.
Creates its own registry atom internally.
Use [`challenge-registry`](#challenge-registry) to get the registry for `sni-alpn-ssl-context`.
Returns a solver map with `:present`, `:cleanup`, and `:registry`.
```clojure
(def solver (integrated-solver))
(jetty-ext/sni-alpn-ssl-context lookup-fn (challenge-registry solver))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/tls_alpn.clj#L159-L195)
---
## switchable-solver
```clojure
(switchable-solver {:keys [port] :or {port 443}})
```
Create a TLS-ALPN-01 solver that can switch from bootstrap to integrated mode.
Returns a solver map with `:present`, `:cleanup`, `:switch-to-integrated!`, and `:registry`.
Pass directly to the automation system’s `:solvers` config.
Creates its own registry atom internally.
Use [`challenge-registry`](#challenge-registry) to get the registry for `sni-alpn-ssl-context`.
Starts in bootstrap mode (starts temporary server for initial cert).
Call [`switch-to-integrated`](#switch-to-integrated) after your TLS server starts so renewals
use the integrated solver (registers in registry for KeyManager to serve).
| | |
| --- | --- |
| name | description |
| `opts` | Options map with `:port` (default 443) |
```clojure
(def solver (switchable-solver {:port 8443}))
;; Use solver with automation
(auto/start {:solvers {:tls-alpn-01 solver} ...})
(auto/manage-domains system ["example.com"])
;; Pass registry to sni-alpn-ssl-context
(jetty-ext/sni-alpn-ssl-context lookup-fn (challenge-registry solver))
;; After TLS server starts
(switch-to-integrated solver)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/tls_alpn.clj#L197-L237)
---
## switch-to-integrated
```clojure
(switch-to-integrated {:keys [switch-to-integrated!]})
```
Switch a switchable solver from bootstrap to integrated mode.
Call this after your TLS server has started.
Future challenge validations will use the integrated solver
(registers cert data in registry for the KeyManager to serve).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/tls_alpn.clj#L239-L246)
---
## challenge-registry
```clojure
(challenge-registry {:keys [registry]})
```
Get the challenge registry atom from a solver.
Use this to pass the registry to `sni-alpn-ssl-context` for ALPN challenge support.
```clojure
(def solver (switchable-solver {:port 443}))
(jetty-ext/sni-alpn-ssl-context lookup-fn (challenge-registry solver))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/acme/solver/tls_alpn.clj#L248-L258)
## ol.clave.automation.impl.cache
# ol.clave.automation.impl.cache
In-memory certificate cache for the automation layer.
The cache provides fast certificate lookup for TLS handshakes and
iteration for maintenance loop. Certificates are indexed by SAN
for efficient domain-based lookups.
## cache-certificate
```clojure
(cache-certificate cache_ bundle)
```
Add or update a certificate in the cache.
If `:capacity` is set in the cache and adding would exceed it,
one random certificate is evicted first.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {} :capacity nil} |
| `bundle` | Certificate bundle with :hash and :names |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L44-L70)
---
## lookup-cert
```clojure
(lookup-cert cache_ hostname)
```
Find certificate for hostname.
Tries exact match first, then wildcard match.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {}} |
| `hostname` | Hostname to look up |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L80-L96)
---
## remove-certificate
```clojure
(remove-certificate cache_ bundle)
```
Remove a certificate from the cache.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {} :capacity nil} |
| `bundle` | Certificate bundle with :hash and :names to remove |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L98-L112)
---
## update-ocsp-staple
```clojure
(update-ocsp-staple cache_ hash ocsp-response)
```
Update OCSP staple in existing cached bundle.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {} :capacity nil} |
| `hash` | Hash of the certificate to update |
| `ocsp-response` | New OCSP staple data |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L114-L128)
---
## update-ari-data
```clojure
(update-ari-data cache_ hash ari-data)
```
Update ARI data in existing cached bundle.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {} :capacity nil} |
| `hash` | Hash of the certificate to update |
| `ari-data` | ARI data with `:suggested-window`, `:selected-time`, `:retry-after` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L130-L143)
---
## mark-managed
```clojure
(mark-managed cache_ hash)
```
Set the :managed flag on a cached bundle.
Used when a previously-cached (unmanaged) certificate becomes managed
via `manage-domains` after passing validation.
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {}} |
| `hash` | Hash of the certificate to update |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L145-L160)
---
## newer-than-cache?
```clojure
(newer-than-cache? stored-bundle cached-bundle)
```
Check if a stored certificate is newer than the cached version.
Compares certificates by their `:not-before` timestamp. Returns true
if the stored certificate was issued after the cached one.
| | |
| --- | --- |
| key | description |
| `stored-bundle` | Certificate bundle from storage |
| `cached-bundle` | Certificate bundle from cache |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L162-L175)
---
## hash-certificate
```clojure
(hash-certificate cert-chain)
```
Compute a consistent hash of certificate chain bytes.
Uses SHA-256 to produce a unique identifier for a certificate chain.
The hash is stable: same input always produces the same output.
| | |
| --- | --- |
| key | description |
| `cert-chain` | Vector of byte arrays (certificate chain in DER or PEM format) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L185-L199)
---
## create-bundle
```clojure
(create-bundle certs private-key issuer-key managed?)
```
Create a certificate bundle from ACME response data.
Extracts SANs, computes hash, and creates a complete bundle map
suitable for caching and TLS use.
| | |
| --- | --- |
| key | description |
| `certs` | Vector of X509Certificate objects (chain) |
| `private-key` | Private key for the certificate |
| `issuer-key` | Identifier for the issuer (e.g., CA directory host) |
| `managed?` | Whether cert is actively managed for renewal |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L218-L244)
---
## handle-command-result
```clojure
(handle-command-result cache_ cmd result)
```
Update cache based on command result.
Handles cache updates for different command types:
- `:obtain-certificate` success: adds new certificate to cache
- `:renew-certificate` success: removes old cert, adds new cert
- `:fetch-ocsp` success: updates OCSP staple in existing bundle
Does nothing on failure (`:status :error`).
| | |
| --- | --- |
| key | description |
| `cache_` | Atom containing {:certs {} :index {}} |
| `cmd` | Command descriptor with `:command` and `:bundle` |
| `result` | Result map with `:status` and command-specific data |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/cache.clj#L246-L283)
## ol.clave.automation.impl.config
# ol.clave.automation.impl.config
## resolve-config
```clojure
(resolve-config system domain)
```
Merge global config with per-domain overrides.
Returns the resolved configuration for a specific domain by merging
the global config with any per-domain overrides from config-fn.
If config-fn is nil or returns nil, returns the global config unchanged.
| | |
| --- | --- |
| key | description |
| `system` | System map containing `:config` and optional `:config-fn` |
| `domain` | Domain name to resolve configuration for |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L12-L30)
---
## select-issuer
```clojure
(select-issuer config)
```
Select issuers based on the issuer-selection policy.
Returns the issuers in the appropriate order based on `:issuer-selection`:
- `:in-order` (default) - return issuers in original order
- `:shuffle` - return issuers in random order
| | |
| --- | --- |
| key | description |
| `config` | Configuration with `:issuers` and optional `:issuer-selection` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L32-L47)
---
## lets-encrypt-production-url
Let’s Encrypt production directory URL.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L49-L51)
---
## default-config
```clojure
(default-config)
```
Returns the default configuration for the automation layer.
Default values:
- Issuer: Let’s Encrypt production
- Key type: P256 (ECDSA)
- OCSP: enabled, must-staple disabled
- ARI: enabled
- Key reuse: disabled
- Cache capacity: unlimited
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L53-L72)
---
## issuer-key-from-url
```clojure
(issuer-key-from-url url)
```
Extract issuer key from directory URL.
Returns a unique identifier for the issuer based on the URL’s host and path.
| | |
| --- | --- |
| key | description |
| `url` | ACME directory URL |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L92-L116)
---
## cert-storage-key
```clojure
(cert-storage-key issuer-key domain)
```
Generate storage key for a certificate PEM file.
Format: `certificates/{issuer-key}/{domain}/{domain}.crt`
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `domain` | Primary domain name |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L118-L130)
---
## key-storage-key
```clojure
(key-storage-key issuer-key domain)
```
Generate storage key for a private key PEM file.
Format: `certificates/{issuer-key}/{domain}/{domain}.key`
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `domain` | Primary domain name |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L132-L144)
---
## meta-storage-key
```clojure
(meta-storage-key issuer-key domain)
```
Generate storage key for certificate metadata EDN file.
Format: `certificates/{issuer-key}/{domain}/{domain}.edn`
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `domain` | Primary domain name |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L146-L158)
---
## certs-prefix
```clojure
(certs-prefix issuer-key)
```
Generate storage prefix for listing certificates under an issuer.
Format: `certificates/{issuer-key}`
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L160-L169)
---
## account-private-key-storage-key
```clojure
(account-private-key-storage-key issuer-key)
```
Generate storage key for an account private key PEM file.
Format: `accounts/{issuer-key}/account.key`
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L171-L180)
---
## account-public-key-storage-key
```clojure
(account-public-key-storage-key issuer-key)
```
Generate storage key for an account public key PEM file.
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L182-L189)
---
## account-registration-storage-key
```clojure
(account-registration-storage-key issuer-key)
```
Generate storage key for account registration EDN.
Format: `accounts/{issuer-key}/registration.edn`
Contains the account KID (URL) returned by the CA after registration,
allowing subsequent operations to skip the newAccount call.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L191-L199)
---
## ocsp-storage-key
```clojure
(ocsp-storage-key issuer-key domain)
```
Generate storage key for an OCSP staple file.
Format: `certificates/{issuer-key}/{domain}/{domain}.ocsp`
The OCSP staple is stored as raw DER-encoded bytes.
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `domain` | Primary domain name |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L201-L215)
---
## compromised-key-storage-key
```clojure
(compromised-key-storage-key domain timestamp)
```
Generate storage key for archiving a compromised private key.
Format: `keys/{domain}.compromised.{timestamp}`
Compromised keys are archived for audit purposes and never reused.
| | |
| --- | --- |
| key | description |
| `domain` | Primary domain name |
| `timestamp` | ISO-8601 timestamp when key was marked compromised |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L217-L231)
---
## ari-storage-key
```clojure
(ari-storage-key issuer-key domain)
```
Generate storage key for ARI (ACME Renewal Information) data.
Format: `certificates/{issuer-key}/{domain}/{domain}.ari.edn`
The ARI data is stored as EDN containing suggested-window, selected-time,
and retry-after.
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `domain` | Primary domain name |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L233-L248)
---
## challenge-token-storage-key
```clojure
(challenge-token-storage-key issuer-key identifier)
```
Generate storage key for a challenge token (distributed solving).
Format: `challenge_tokens/{issuer-key}/{identifier}.edn`
Used to store challenge data so any instance in a cluster can serve
the challenge response for HTTP-01 or TLS-ALPN-01 validation.
| | |
| --- | --- |
| key | description |
| `issuer-key` | Issuer identifier (hostname from directory URL) |
| `identifier` | Domain or IP address being validated |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L250-L265)
---
## select-chain
```clojure
(select-chain preference chains)
```
Select a certificate chain based on preference.
Preferences:
- `:any` (default) - return first chain offered
- `:shortest` - return chain with fewest certificates
- `{:root "Root CA Name"}` - return chain with matching root name
Returns nil if chains is empty.
Falls back to first chain if root name not found.
| | |
| --- | --- |
| key | description |
| `preference` | Chain preference (`:any`, `:shortest`, or `{:root name}`) |
| `chains` | Sequence of chain maps with `:chain` (certs) and `:root-name` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/config.clj#L267-L299)
## ol.clave.automation.impl.decisions
# ol.clave.automation.impl.decisions
Pure decision functions for the automation layer.
These functions contain all the logic for determining what maintenance
actions are needed for certificates.
## *renewal-threshold*
Fraction of lifetime remaining that triggers renewal.
Default 0.33 means renew when 1/3 of lifetime remains.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L10-L13)
---
## *emergency-override-ari-threshold*
Fraction of lifetime remaining that overrides ARI guidance.
Default 0.05 (1/20) = 5% of lifetime.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L15-L18)
---
## *emergency-critical-threshold*
Fraction of lifetime remaining that triggers critical emergency.
Default 0.02 (1/50) = 2% of lifetime.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L20-L23)
---
## *emergency-min-intervals*
Minimum maintenance intervals before expiration for critical status.
Default 5 intervals.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L25-L28)
---
## *ocsp-refresh-threshold*
Fraction of OCSP validity window that triggers refresh.
Default 0.5 = refresh at 50% of validity window.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L30-L33)
---
## *short-lived-threshold-ms*
Threshold for short-lived certificates (7 days in ms).
Certificates shorter than this use different renewal logic.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L35-L38)
---
## *max-retry-duration-ms*
Maximum duration to retry a failing operation (30 days).
After this duration, the operation fails permanently.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L40-L43)
---
## short-lived-cert?
```clojure
(short-lived-cert? bundle)
```
Check if a certificate is short-lived (< 7 days lifetime).
Short-lived certificates (like those from ACME staging or specialized CAs)
require different renewal timing logic.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L45-L54)
---
## ari-suggests-renewal?
```clojure
(ari-suggests-renewal? bundle now maintenance-interval-ms)
```
Check if ARI data suggests renewal is due.
Per RFC 9710, returns true if current time is past the cutoff, where
cutoff = selected-time minus maintenance-interval. This ensures we don’t
miss the renewal window if maintenance runs just after the selected time.
Returns false if no ARI data or no selected-time is present.
| | |
| --- | --- |
| key | description |
| `bundle` | Certificate bundle with optional `:ari-data` |
| `now` | Current instant |
| `maintenance-interval-ms` | Maintenance loop interval in milliseconds |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L56-L76)
---
## calculate-ari-renewal-time
```clojure
(calculate-ari-renewal-time ari-data)
(calculate-ari-renewal-time ari-data rng)
```
Calculate a random time within the ARI suggested renewal window.
| | |
| --- | --- |
| key | description |
| `ari-data` | ARI data with `:suggested-window` [start-instant end-instant] |
| `rng` | `java.util.Random` instance for random selection (optional) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L78-L93)
---
## needs-renewal?
```clojure
(needs-renewal? bundle now maintenance-interval-ms)
```
Check if certificate needs renewal based on expiration, ARI, and emergency.
Returns true if:
- Less than 5% (`**emergency-override-ari-threshold**`) of lifetime remains
(supersedes ARI guidance for safety), OR
- ARI selected-time cutoff has passed (per RFC 9710), OR
- Less than `**renewal-threshold**` (default 1/3) of lifetime remains
The emergency override ensures we never rely solely on ARI when the
certificate is dangerously close to expiration.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L95-L119)
---
## emergency-renewal?
```clojure
(emergency-renewal? bundle now maintenance-interval-ms)
```
Check if certificate is dangerously close to expiration.
Tiered thresholds:
- `:critical` - 1/50 (2%) lifetime remaining OR fewer than 5 maintenance intervals
- `:override-ari` - 1/20 (5%) lifetime remaining, overrides ARI guidance
- `nil` - no emergency
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L121-L144)
---
## needs-ocsp-refresh?
```clojure
(needs-ocsp-refresh? bundle config now)
```
Check if OCSP staple needs refresh.
Returns true if:
- OCSP is enabled in config AND
- Certificate is not short-lived (>= 7 days) AND
- Staple is nil OR past 50% of validity window
Returns false if:
- OCSP is disabled, OR
- Certificate is short-lived (< 7 days lifetime)
Short-lived certificates don’t benefit from OCSP stapling because
the certificate will expire before the OCSP response provides value.
| | |
| --- | --- |
| key | description |
| `bundle` | Certificate bundle with optional `:ocsp-staple` |
| `config` | Configuration with `:ocsp` map containing `:enabled` key |
| `now` | Current instant |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L169-L196)
---
## check-cert-maintenance
```clojure
(check-cert-maintenance bundle config now maintenance-interval-ms)
```
Returns commands needed for this certificate.
Pure function that examines certificate state and returns a vector
of command descriptors. Does not perform any I/O.
| | |
| --- | --- |
| key | description |
| `bundle` | Certificate bundle from cache |
| `config` | Resolved configuration for this domain |
| `now` | Current instant |
| `maintenance-interval-ms` | Maintenance loop interval in milliseconds |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L198-L221)
---
## command-key
```clojure
(command-key cmd)
```
Generate a key for command deduplication.
Returns a vector of [command-type domain] that uniquely identifies
a command. Commands with the same key are considered duplicates
and can be deduplicated by the job queue.
| | |
| --- | --- |
| key | description |
| `cmd` | Command descriptor with `:command` and `:domain` keys |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L223-L234)
---
## fast-command?
```clojure
(fast-command? cmd)
```
Check if a command is fast.
| | |
| --- | --- |
| key | description |
| `cmd` | Command descriptor with `:command` key |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L240-L247)
---
## classify-error
```clojure
(classify-error ex)
```
Classify an exception into an error category.
Categories:
- `:network-error` - connection failures, DNS issues, timeouts
- `:rate-limited` - HTTP 429 responses
- `:acme-error` - ACME protocol errors (4xx responses)
- `:server-error` - server-side failures (5xx responses)
- `:config-error` - configuration problems
- `:storage-error` - I/O and storage failures
- `:unknown` - unrecognized exceptions
| | |
| --- | --- |
| key | description |
| `ex` | Exception to classify |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L261-L302)
---
## retryable-error?
```clojure
(retryable-error? error-type)
```
Check if an error type should be retried.
Retryable errors:
- `:network-error` - transient network issues
- `:rate-limited` - should back off and retry
- `:server-error` - server may recover
- `:storage-error` - storage may become available
Non-retryable errors:
- `:acme-error` - client errors unlikely to succeed
- `:config-error` - configuration must be fixed
- `:unknown` - cannot determine if safe to retry
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L308-L322)
---
## event-for-result
```clojure
(event-for-result cmd result)
```
Create an event from a command result.
Event types by command and status:
- `:obtain-certificate` success -> `:certificate-obtained`
- `:renew-certificate` success -> `:certificate-renewed`
- `:obtain-certificate` error -> `:certificate-failed`
- `:renew-certificate` error -> `:certificate-failed`
- `:fetch-ocsp` success -> `:ocsp-stapled`
- `:fetch-ocsp` error -> `:ocsp-failed`
| | |
| --- | --- |
| key | description |
| `cmd` | Command descriptor with `:command` and `:domain` |
| `result` | Result map with `:status` (`:success` or `:error`) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L326-L410)
---
## create-certificate-loaded-event
```clojure
(create-certificate-loaded-event bundle)
```
Create an event for a certificate loaded from storage.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L412-L421)
---
## retry-intervals
Exponential backoff intervals for failed operations (in milliseconds).
The pattern is front-loaded: aggressive early retries (1-2 min) catch
transient failures quickly, then backs off through medium intervals
(5-20 min) for rate-limiting or brief outages, then longer intervals
(30 min - 1 hr) for issues requiring propagation or human intervention.
Caps at 6 hours to avoid wasting resources during persistent outages.
Repeated consecutive values control dwell time at each tier before
escalating (e.g., three 10-minute intervals means 3 attempts at that
tier before moving to 20 minutes).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L425-L461)
---
## calculate-maintenance-jitter
```clojure
(calculate-maintenance-jitter maintenance-jitter)
(calculate-maintenance-jitter maintenance-jitter rng)
```
Calculate random jitter for maintenance loop scheduling.
Returns a random value in [0, maintenance-jitter) to spread out
maintenance operations and avoid thundering herd problems.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/decisions.clj#L463-L471)
## ol.clave.automation.impl.domain
# ol.clave.automation.impl.domain
Domain validation for the automation layer.
## validate-domain
```clojure
(validate-domain domain config)
```
Validate a domain name for ACME certificate issuance.
Returns nil if the domain is valid, or an error map if it cannot
receive ACME certificates.
Error map structure:
- `:error` - always `:invalid-domain`
- `:message` - human-readable explanation
Security validations:
- Directory traversal patterns (.., /, \)
- Invalid characters
- Invalid format (leading/trailing dots)
- Wildcard format and DNS-01 solver requirement
- (when using a public ca) the following are not allowed
- localhost, .local, .internal, .test TLDs
- private IP addresses
| | |
| --- | --- |
| key | description |
| `domain` | Domain name to validate |
| `config` | Configuration with :solvers map |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/domain.clj#L100-L156)
## ol.clave.automation.impl.system
# ol.clave.automation.impl.system
System lifecycle and component wiring for the automation layer.
The system map contains all components and is passed to internal functions.
Components access what they need via destructuring.
## *maintenance-interval-ms*
Maintenance loop interval in milliseconds (1 hour).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L36-L38)
---
## *maintenance-jitter-ms*
Maximum jitter for maintenance loop (5 minutes).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L40-L42)
---
## *fast-semaphore-permits*
Concurrent fast command permits (OCSP, ARI).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L44-L46)
---
## *slow-semaphore-permits*
Concurrent slow command permits (obtain, renew).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L48-L50)
---
## *shutdown-timeout-ms*
Timeout for graceful shutdown in milliseconds (30 seconds).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L52-L54)
---
## *config-fn-timeout-ms*
Timeout for config-fn calls in milliseconds (5 seconds).
If config-fn takes longer than this, the domain is skipped.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L56-L59)
---
## submit-command!
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L62-L62)
---
## trigger-maintenance
```clojure
(trigger-maintenance system)
```
See [`ol.clave.automation/trigger-maintenance`](api/ol-clave-automation.adoc#trigger-maintenance)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L388-L391)
---
## create
```clojure
(create config)
```
See [`ol.clave.automation/create`](api/ol-clave-automation.adoc#create)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L412-L420)
---
## start
```clojure
(start system)
```
See [`ol.clave.automation/start`](api/ol-clave-automation.adoc#start)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L422-L428)
---
## stop
```clojure
(stop system)
```
See [`ol.clave.automation/stop`](api/ol-clave-automation.adoc#stop)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L430-L444)
---
## started?
```clojure
(started? system)
```
See [`ol.clave.automation/started?`](api/ol-clave-automation.adoc#started-QMARK-)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L446-L449)
---
## lookup-cert
```clojure
(lookup-cert system hostname)
```
See [`ol.clave.automation/lookup-cert`](api/ol-clave-automation.adoc#lookup-cert)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L488-L507)
---
## manage-domains
```clojure
(manage-domains system domains)
```
See [`ol.clave.automation/manage-domains`](api/ol-clave-automation.adoc#manage-domains)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1196-L1232)
---
## unmanage-domains
```clojure
(unmanage-domains system domains)
```
See [`ol.clave.automation/unmanage-domains`](api/ol-clave-automation.adoc#unmanage-domains)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1234-L1246)
---
## list-domains
```clojure
(list-domains system)
```
See [`ol.clave.automation/list-domains`](api/ol-clave-automation.adoc#list-domains)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1248-L1257)
---
## get-domain-status
```clojure
(get-domain-status system domain)
```
See [`ol.clave.automation/get-domain-status`](api/ol-clave-automation.adoc#get-domain-status)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1259-L1267)
---
## has-valid-cert?
```clojure
(has-valid-cert? system domain)
```
See [`ol.clave.automation/has-valid-cert?`](api/ol-clave-automation.adoc#has-valid-cert-QMARK-)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1269-L1272)
---
## get-event-queue
```clojure
(get-event-queue system)
```
See [`ol.clave.automation/get-event-queue`](api/ol-clave-automation.adoc#get-event-queue)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1274-L1282)
---
## renew-managed
```clojure
(renew-managed system)
```
See [`ol.clave.automation/renew-managed`](api/ol-clave-automation.adoc#renew-managed)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1284-L1297)
---
## revoke
```clojure
(revoke system certificate opts)
```
See [`ol.clave.automation/revoke`](api/ol-clave-automation.adoc#revoke)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation/impl/system.clj#L1299-L1337)
## ol.clave.automation
# ol.clave.automation
Public API for the ACME certificate automation layer.
The automation layer manages TLS certificate lifecycle automatically:
- Obtains certificates for managed domains
- Renews certificates before expiration
- Handles OCSP stapling
- Provides events for monitoring
## Quick Start
```clojure
(require '[ol.clave.automation :as auto]
'[ol.clave.storage.file :as fs])
;; Create the automation system
(def system (auto/create {:storage (fs/file-storage {:root "/var/lib/acme"})
:issuers [{:directory-url "https://acme-v02.api.letsencrypt.org/directory"
:email "admin@example.com"}]
:solvers {:http-01 my-http-solver}}))
;; Optionally get the event queue before starting
(def queue (auto/get-event-queue system))
;; Start the maintenance loop
(auto/start system)
;; Add domains to manage
(auto/manage-domains system ["example.com"])
;; Look up certificate for TLS handshake
(auto/lookup-cert system "example.com")
;; Stop the system
(auto/stop system)
```
## Configuration
The config map supports:
| | |
| --- | --- |
| key | description |
| `:storage` | Storage implementation (required) |
| `:issuers` | Vector of issuer configs with `:directory-url` and optional `:email`, `:eab` |
| `:issuer-selection` | `:in-order` (default) or `:shuffle` |
| `:key-type` | `:p256` (default), `:p384`, `:rsa2048`, `:rsa4096`, `:rsa8192`, `:ed25519` |
| `:key-reuse` | Reuse private key on renewal (default false) |
| `:solvers` | Map of solver types to implementations |
| `:ocsp` | OCSP config with `:enabled`, `:must-staple` |
| `:ari` | ARI config with `:enabled` |
| `:cache-capacity` | Max certificates in cache (nil = unlimited) |
| `:config-fn` | Function: domain -> config overrides |
| `:http-client` | HTTP client options for ACME requests |
## create
```clojure
(create config)
```
Creates the automation system without starting the maintenance loop.
Returns a system handle that is not yet started.
Throws if configuration is invalid or storage cannot be initialized.
After calling this function you might be interested in [`get-event-queue`](#get-event-queue) and [`start`](#start).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L58-L67)
---
## start
```clojure
(start system)
```
Starts the maintenance loop on a created system.
Call this after [`create`](#create) to begin automatic certificate management.
Idempotent: calling on an already-started system is a no-op.
Returns the system handle.
See also [`create`](#create).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L69-L79)
---
## stop
```clojure
(stop system)
```
Stops the automation system.
Signals the maintenance loop to stop, waits for in-flight operations,
and releases resources.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L86-L96)
---
## started?
```clojure
(started? system)
```
Returns true if the system is in started state.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L98-L105)
---
## manage-domains
```clojure
(manage-domains system domains)
```
Adds domains to management, triggering immediate certificate obtain.
Returns `nil` on success.
Throws with `:errors` in ex-data if any domain is invalid.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `domains` | Vector of domain names to manage |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L107-L118)
---
## unmanage-domains
```clojure
(unmanage-domains system domains)
```
Removes domains from management.
Stops renewal and maintenance for these domains.
Certificates remain in storage but are no longer actively managed.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `domains` | Vector of domain names to unmanage |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L120-L131)
---
## lookup-cert
```clojure
(lookup-cert system hostname)
```
Finds a certificate for a hostname.
Tries exact match first, then wildcard match.
Returns the certificate bundle or nil if not found.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `hostname` | Hostname to look up |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L133-L144)
---
## list-domains
```clojure
(list-domains system)
```
Lists all managed domains with status.
Returns a vector of maps with `:domain`, `:status`, and `:not-after`.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L146-L155)
---
## get-domain-status
```clojure
(get-domain-status system domain)
```
Gets detailed status for a specific domain.
Returns a map with `:domain`, `:status`, `:not-after`, `:issuer`,
`:needs-renewal`, or nil if domain is not managed.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `domain` | Domain name to check |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L157-L168)
---
## has-valid-cert?
```clojure
(has-valid-cert? system domain)
```
Returns true if the system has a valid certificate for the domain.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `domain` | Domain name to check |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L170-L178)
---
## get-event-queue
```clojure
(get-event-queue system)
```
Gets the event queue handle for monitoring.
The queue is created lazily on first call. Subsequent calls return
the same queue instance.
Returns a `java.util.concurrent.LinkedBlockingQueue`
Poll with `.poll`, `.poll(timeout, unit)`, or `.take`.
When the system is stopped via [`stop`](#stop), a `:ol.clave/shutdown` keyword
is placed on the queue. Consumers should check for this sentinel to
know when to stop polling.
| | |
| --- | --- |
| key | description |
| `system` | System handle from [`start`](#start) |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L180-L197)
---
## renew-managed
```clojure
(renew-managed system)
```
Forces renewal of all managed certificates.
Submits renewal commands for every managed certificate in the cache.
Commands are submitted asynchronously - this function returns immediately.
Normally certificates are renewed automatically. Use this for
testing or when you need to force renewal.
Returns the number of certificates queued for renewal.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L199-L214)
---
## revoke
```clojure
(revoke system certificate opts)
```
Revokes a certificate.
The `certificate` parameter can be:
- A domain string - looks up the certificate from the cache
- A bundle map - uses the bundle directly
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
| `certificate` | Domain string or bundle map |
| `opts` | Options map (see below) |
Options:
| | |
| --- | --- |
| key | description |
| `:remove-from-storage` | When true, deletes certificate files from storage |
| `:reason` | RFC 5280 revocation reason code (0-6, 8-10) |
Returns `{:status :success}` on successful revocation,
or `{:status :error :message ...}` on failure.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L216-L239)
---
## trigger-maintenance
```clojure
(trigger-maintenance system)
```
Manually triggers a maintenance cycle.
This is primarily useful for testing - in normal operation the
maintenance loop runs automatically at regular intervals.
| | |
| --- | --- |
| key | description |
| `system` | System handle from `start` |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/automation.clj#L241-L251)
## ol.clave.certificate.impl.csr
# ol.clave.certificate.impl.csr
Pure Clojure PKCS#10 CSR generation with no external dependencies.
Supports RSA (2048, 3072, 4096), ECDSA (P-256, P-384), and Ed25519.
Automatically handles IDNA conversion for internationalized domains.
Validates and normalizes Subject Alternative Names.
No other extensions or key types are supported.
If you need more features then you will need to use external tools to provide your own CSR.
## create-csr
```clojure
(create-csr key-pair sans & [opts])
```
See [`ol.clave.certificate/csr`](api/ol-clave-certificate.adoc#csr)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/csr.clj#L342-L402)
## ol.clave.certificate.impl.keygen
# ol.clave.certificate.impl.keygen
Key generation utilities for creating keypairs to back TLS certificates.
Supports multiple key types for certificate signing:
- ECDSA curves: P-256 (secp256r1), P-384 (secp384r1)
- EdDSA: Ed25519
- RSA: 2048, 4096, and 8192 bit keys
Note: ACME account keys are managed separately via `ol.clave.impl.crypto`.
This namespace is specifically for certificate keypairs.
## supported-key-types
Set of supported key types for certificate keypair generation.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L22-L29)
---
## pem-encode
```clojure
(pem-encode type der)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L31-L36)
---
## private-key->pem
```clojure
(private-key->pem private-key)
```
See [`ol.clave.certificate/private-key->pem`](api/ol-clave-certificate.adoc#private-key--GT-pem)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L38-L41)
---
## gen-ed25519
```clojure
(gen-ed25519)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L48-L51)
---
## gen-p256
```clojure
(gen-p256)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L53-L57)
---
## gen-p384
```clojure
(gen-p384)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L59-L63)
---
## gen-rsa2048
```clojure
(gen-rsa2048)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L65-L69)
---
## gen-rsa4096
```clojure
(gen-rsa4096)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L71-L75)
---
## gen-rsa8192
```clojure
(gen-rsa8192)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L77-L81)
---
## generate
```clojure
(generate key-type)
```
See [`ol.clave.certificate/keypair`](api/ol-clave-certificate.adoc#keypair)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/keygen.clj#L83-L95)
## ol.clave.certificate.impl.ocsp
# ol.clave.certificate.impl.ocsp
Pure Clojure OCSP (Online Certificate Status Protocol) utilities.
Provides functionality to:
- Extract OCSP responder URLs from certificates
- Fetch OCSP responses from responders
- Parse and validate OCSP responses
## extract-ocsp-urls
```clojure
(extract-ocsp-urls cert)
```
Extract OCSP responder URLs from a certificate’s AIA extension.
Returns a vector of OCSP URLs, or empty vector if none found.
| | |
| --- | --- |
| key | description |
| `cert` | X509Certificate to extract OCSP URLs from |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/ocsp.clj#L61-L90)
---
## create-ocsp-request
```clojure
(create-ocsp-request cert issuer)
```
Create an OCSP request for a certificate.
Requires both the leaf certificate and its issuer certificate.
Returns the DER-encoded OCSP request bytes.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/ocsp.clj#L193-L202)
---
## parse-ocsp-response
```clojure
(parse-ocsp-response response-bytes)
```
Parse an OCSP response and extract status information.
Returns a map with:
- `:status` - One of :good, :revoked, :unknown, or :error
- `:this-update` - When this response was generated
- `:next-update` - When the response expires
- `:revocation-time` - For revoked certs, when it was revoked
- `:revocation-reason` - For revoked certs, the reason code
- `:raw-bytes` - The original DER-encoded response
- `:error-code` - For error responses, the OCSP error code
- `:message` - For error responses, the error message
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/ocsp.clj#L342-L387)
---
## fetch-ocsp-response
```clojure
(fetch-ocsp-response cert issuer responder-url http-opts)
```
Fetch OCSP response for a certificate from the specified responder.
| | |
| --- | --- |
| key | description |
| `cert` | The X509Certificate to check |
| `issuer` | The issuer certificate |
| `responder-url` | URL of the OCSP responder |
| `http-opts` | HTTP client options map |
Returns a result map:
- On success: `{:status :success :ocsp-response {...}}`
- On failure: `{:status :error :message "..."}`
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/ocsp.clj#L391-L428)
---
## fetch-ocsp-for-bundle
```clojure
(fetch-ocsp-for-bundle bundle http-opts responder-overrides)
```
Fetch OCSP response for a certificate bundle.
Extracts the OCSP URL from the leaf certificate and fetches the response.
Supports responder URL overrides for testing.
| | |
| --- | --- |
| key | description |
| `bundle` | Certificate bundle with `:certificate` chain |
| `http-opts` | HTTP client options |
| `responder-overrides` | Optional map of original-url -> override-url |
Returns a result map:
- On success: `{:status :success :ocsp-response {...}}`
- On failure: `{:status :error :message "..."}`
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/ocsp.clj#L430-L468)
## ol.clave.certificate.impl.parse
# ol.clave.certificate.impl.parse
## parse-pem-chain
```clojure
(parse-pem-chain pem)
```
Parse a PEM-encoded certificate chain into structured data.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/parse.clj#L36-L44)
---
## parse-pem-response
```clojure
(parse-pem-response resp url)
```
Validate and parse a PEM certificate response.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/parse.clj#L46-L61)
## ol.clave.certificate.impl.tls-alpn
# ol.clave.certificate.impl.tls-alpn
TLS-ALPN-01 challenge certificate generation.
Builds self-signed X.509 v3 certificates containing the acmeValidationV1
extension required by RFC 8737 for TLS-ALPN-01 ACME challenges.
## acme-tls-1-protocol
ALPN protocol identifier for TLS-ALPN-01 challenges (RFC 8737 Section 6.2).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/tls_alpn.clj#L25-L27)
---
## tlsalpn01-challenge-cert
```clojure
(tlsalpn01-challenge-cert identifier key-authorization)
```
See [`ol.clave.acme.challenge/tlsalpn01-challenge-cert`](api/ol-clave-acme-challenge.adoc#tlsalpn01-challenge-cert)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/tls_alpn.clj#L184-L223)
## ol.clave.certificate.impl.x509
# ol.clave.certificate.impl.x509
X.509 encoding utilities for certificates and CSRs.
We don’t implement all of X.509 (lol), we implement just enough to:
- generate CSRs
- generate TLS-ALPN-01 challenge certificates
Provides:
- IDNA encoding for internationalized domain names in SANs
- GeneralName encoding for Subject Alternative Names
- Extension encoding for certificate extensions
## idna-encode
```clojure
(idna-encode domain)
```
Convert Unicode domain to ASCII (Punycode) using IDNA.
Normalizes to lowercase first, then applies IDNA conversion.
Throws ex-info with ::errors/invalid-idna on failure.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/x509.clj#L22-L36)
---
## encode-extension
```clojure
(encode-extension oid critical? value)
```
Encode a single X.509 extension.
Arguments:
oid - OID string (e.g., "2.5.29.17" for subjectAltName)
critical? - Boolean indicating if extension is critical
value - DER-encoded extension value bytes
Returns DER-encoded extension SEQUENCE.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/x509.clj#L38-L53)
---
## encode-dns-general-name
```clojure
(encode-dns-general-name domain)
```
Encode DNS GeneralName (context tag 2).
Applies IDNA encoding to the domain before encoding.
Returns DER-encoded GeneralName for DNS identifier.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/x509.clj#L55-L62)
---
## encode-ip-general-name
```clojure
(encode-ip-general-name ip-bytes)
```
Encode IP GeneralName (context tag 7) from raw bytes.
Arguments:
ip-bytes - Raw IP address bytes (4 bytes for IPv4, 16 bytes for IPv6)
Returns DER-encoded GeneralName for IP identifier.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate/impl/x509.clj#L64-L72)
## ol.clave.certificate
# ol.clave.certificate
Certificate acquisition and management.
This namespace provides high-level functions for obtaining TLS certificates
from ACME servers, abstracting away the multi-step protocol workflow.
The main entry point is [`obtain`](#obtain) which orchestrates the complete ACME
workflow: creating orders, solving challenges, finalizing with a CSR, and
downloading the issued certificate.
Solvers are maps containing functions that handle challenge provisioning:
- `:present` (required) - provisions resources for ACME validation
- `:cleanup` (required) - removes provisioned resources
- `:wait` (optional) - waits for slow provisioning (e.g., DNS propagation)
- `:payload` (optional) - generates custom challenge response payload
See `ol.clave.solver.http` for an HTTP-01 solver with Ring middleware.
See also `ol.clave.commands` for low-level plumbing operations.
## validate-solvers
```clojure
(validate-solvers solvers)
```
Validate that all solvers have required :present and :cleanup functions.
Solvers are maps that must contain:
- `:present` - function to provision challenge resources
- `:cleanup` - function to clean up provisioned resources
Optional keys are allowed (permissive mode):
- `:wait` - function for slow provisioning operations
- `:payload` - function to generate custom challenge payload
- Any other keys (for user metadata)
Returns nil on success, throws on validation failure.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L38-L63)
---
## wrap-solver-for-distributed
```clojure
(wrap-solver-for-distributed storage issuer-key storage-key-fn solver)
```
Wraps a solver to store/cleanup challenge tokens in shared storage.
This enables distributed challenge solving where multiple instances
behind a load balancer can serve ACME validation responses.
On `:present`: stores challenge data to storage before calling underlying solver.
On `:cleanup`: calls underlying solver cleanup, then deletes from storage.
The stored data is a JSON map containing the full challenge plus key-authorization,
enabling any instance to reconstruct and serve the response.
| | |
| --- | --- |
| key | description |
| `storage` | Storage implementation for persisting challenge tokens |
| `issuer-key` | Issuer identifier for storage key namespacing |
| `storage-key-fn` | Function `(fn [issuer-key identifier]) -> storage-key` |
| `solver` | The underlying solver map to wrap |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L67-L118)
---
## wrap-solvers-for-distributed
```clojure
(wrap-solvers-for-distributed storage issuer-key storage-key-fn solvers)
```
Wraps all solvers in a map for distributed challenge solving.
| | |
| --- | --- |
| key | description |
| `storage` | Storage implementation |
| `issuer-key` | Issuer identifier |
| `storage-key-fn` | Function to generate storage keys |
| `solvers` | Map of challenge-type -> solver |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L120-L134)
---
## lookup-challenge-token
```clojure
(lookup-challenge-token storage issuer-key storage-key-fn identifier)
```
Lookup a stored challenge token from shared storage.
Returns the challenge data map if found, nil otherwise.
| | |
| --- | --- |
| key | description |
| `storage` | Storage implementation |
| `issuer-key` | Issuer identifier |
| `storage-key-fn` | Function to generate storage keys |
| `identifier` | Domain or IP being validated |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L136-L150)
---
## obtain
```clojure
(obtain the-lease session identifiers cert-keypair solvers opts)
```
Obtain a certificate from an ACME server using configured solvers.
This function automates the complete ACME workflow defined in RFC 8555 Section 7.1:
creating an order, solving authorization challenges, finalizing with a CSR,
and downloading the issued certificate.
Parameters:
| | |
| --- | --- |
| name | description |
| `the-lease` | Lease for cancellation and timeout control |
| `session` | Authenticated ACME session with account key and KID |
| `identifiers` | Vector of identifier maps from `order/create-identifier` |
| `cert-keypair` | KeyPair for the certificate (distinct from account key) |
| `solvers` | Map of challenge type keyword to solver map |
| `opts` | Optional configuration map |
Options map:
| | |
| --- | --- |
| key | description |
| `:not-before` | Requested validity start (java.time.Instant) |
| `:not-after` | Requested validity end (java.time.Instant) |
| `:profile` | ACME profile name when CA supports profiles |
| `:preferred-challenges` | Vector of challenge types in preference order |
| `:poll-interval-ms` | Polling interval for authorization/order |
| `:poll-timeout-ms` | Polling timeout |
Returns `[updated-session result]` where result is a map:
| | |
| --- | --- |
| key | description |
| `:order` | Final order map with status "valid" |
| `:certificates` | Vector of certificate maps |
| `:attempts` | Number of order creation attempts made |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L285-L443)
---
## identifiers-from-sans
```clojure
(identifiers-from-sans sans)
```
Convert a sequence of SAN strings to identifier maps.
Automatically detects IP addresses (IPv4 and IPv6) vs DNS names.
Example:
```clojure
(identifiers-from-sans ["example.com" "192.168.1.1" "2001:db8::1"])
;; => [{:type "dns" :value "example.com"}
;; {:type "ip" :value "192.168.1.1"}
;; {:type "ip" :value "2001:db8::1"}]
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L447-L465)
---
## obtain-for-sans
```clojure
(obtain-for-sans the-lease session sans cert-key solvers)
(obtain-for-sans the-lease session sans cert-key solvers opts)
```
Simplified certificate acquisition for the common case.
Automatically creates identifiers from SAN strings.
Parameters:
| | |
| --- | --- |
| name | description |
| `the-lease` | Lease for cancellation/timeout |
| `session` | Authenticated ACME session |
| `sans` | Vector of SAN strings (domains, IPs) |
| `cert-key` | KeyPair for certificate |
| `solvers` | Map of challenge type keyword to solver map |
| `opts` | Optional configuration map (see [`obtain`](#obtain)) |
Returns `[updated-session result]` as with `obtain-certificate`.
Example:
```clojure
(obtain-certificate-for-sans
(lease/background)
session
["example.com" "www.example.com"]
cert-key
{:http-01 http-solver})
;; With options
(obtain-certificate-for-sans
(lease/background)
session
["example.com"]
cert-key
{:http-01 http-solver :tls-alpn-01 tls-solver}
{:preferred-challenges [:http-01 :tls-alpn-01]})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L467-L506)
---
## csr
```clojure
(csr keypair sans & [opts])
```
Generate a PKCS#10 CSR from a KeyPair
SANs (Subject Alternative Names) are automatically processed:
- Unicode domains are converted to Punycode using IDNA encoding.
- Wildcard usage is validated per RFC 6125 (DNS names only).
- IP address format is validated for both IPv4 and IPv6.
- Values are deduplicated and normalized.
Arguments:
key-pair - java.security.KeyPair (RSA, EC, or EdDSA). Required.
sans - Vector of strings (domain names or IP addresses). Required.
Examples: ["example.com" "*.example.com" "192.0.2.1" "2001:db8::1"]
opts - Map of options. Optional, defaults to {}.
:use-cn? - Boolean. If true, set Subject CN to the first DNS SAN.
Default false. IPs are skipped when searching for CN value.
When false, Subject is empty and all identifiers are in SANs only
(one of three valid options per RFC 8555 Section 7.4).
Returns:
{:csr-pem String - PEM-encoded CSR
:csr-der bytes - Raw DER bytes
:csr-b64url String - Base64URL-encoded DER (no padding) for ACME
:algorithm Keyword - :rsa-2048, :rsa-3072, :rsa-4096, :ec-p256, :ec-p384, or :ed25519
:details Map - Algorithm OIDs, signature info}
Supports RSA (2048, 3072, 4096), ECDSA (P-256, P-384), and Ed25519.
Automatically handles IDNA conversion for internationalized domains.
Validates and normalizes Subject Alternative Names.
No other extensions or key types are supported.
If you need more features then you will need to use external tools to provide your own CSR.
Examples:
;; Modern ACME: no CN in subject (use-cn? = false, the default)
(create-csr kp ["example.com" "*.example.com"])
(create-csr kp ["example.com" "www.example.com"] {})
;; Legacy: CN = first DNS SAN (IPs are skipped)
(create-csr kp ["example.com" "www.example.com"] {:use-cn? true})
;; Mixed DNS and IP SANs (auto-detected)
(create-csr kp ["example.com" "192.0.2.1" "2001:db8::1"])
;; Unicode domains (auto-converted to Punycode)
(create-csr kp ["münchen.example" "www.münchen.example"])
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L508-L555)
---
## private-key->pem
```clojure
(private-key->pem private-key)
```
Encode a private key as PKCS#8 PEM-formatted string.
```clojure
(private-key->pem (.getPrivate keypair))
;; => "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L557-L565)
---
## keypair
```clojure
(keypair)
(keypair key-type)
```
Generate a keypair of the specified type.
`key-type` must be one of [`supported-key-types`](api/ol-clave-certificate-impl-keygen.adoc#supported-key-types):
- `:ed25519` - Ed25519 curve
- `:p256` - ECDSA P-256 (default, recommended)
- `:p384` - ECDSA P-384
- `:rsa2048` - RSA 2048-bit
- `:rsa4096` - RSA 4096-bit
- `:rsa8192` - RSA 8192-bit
If you don’t know which one to choose, just use the default.
Returns a `java.security.KeyPair`.
```clojure
(generate :p256)
;; => #object[java.security.KeyPair ...]
(.getPublic (generate :ed25519))
;; => #object[sun.security.ec.ed.EdDSAPublicKeyImpl ...]
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/certificate.clj#L567-L593)
## ol.clave.crypto.impl.der
# ol.clave.crypto.impl.der
DER (Distinguished Encoding Rules) encoding and decoding for ASN.1 structures.
This namespace provides low-level functions for encoding and decoding ASN.1
data structures according to ITU-T X.690 (DER encoding rules).
We implement just enough to:
- Generate CSRs
- Generate TLS-ALPN-01 challenge certificates
- Parse certificate extensions (AIA, AKI)
- Parse OCSP responses
Encoding functions return byte arrays.
Decoding functions work with byte arrays and return Clojure data structures.
## tag-boolean
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L29-L29)
---
## tag-integer
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L30-L30)
---
## tag-bit-string
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L31-L31)
---
## tag-octet-string
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L32-L32)
---
## tag-null
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L33-L33)
---
## tag-oid
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L34-L34)
---
## tag-enumerated
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L35-L35)
---
## tag-utf8-string
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L36-L36)
---
## tag-printable-string
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L37-L37)
---
## tag-ia5-string
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L38-L38)
---
## tag-utc-time
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L39-L39)
---
## tag-generalized-time
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L40-L40)
---
## tag-sequence
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L41-L41)
---
## tag-set
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L42-L42)
---
## class-context-specific
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L44-L44)
---
## constructed-bit
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L45-L45)
---
## encode-length
```clojure
(encode-length length)
```
Encode DER length octets (short or long form).
Short form (length < 128): single byte with the length value.
Long form (length >= 128): first byte has high bit set and indicates
number of length bytes, followed by length bytes in big-endian order.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L49-L68)
---
## concat-bytes
```clojure
(concat-bytes & arrays)
```
Concatenate multiple byte arrays into a new byte array.
Efficiently copies all input arrays into a single output array.
Returns empty array if no arrays provided.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L70-L83)
---
## der-primitive
```clojure
(der-primitive tag content)
```
Encode DER primitive with given tag and content.
Tag should be a single-byte tag value (e.g., 0x02 for INTEGER).
Content is the raw bytes for this primitive.
Returns complete TLV (tag-length-value) encoded byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L87-L100)
---
## der-constructed
```clojure
(der-constructed tag content)
```
Encode DER constructed with given tag and content.
Tag should include the constructed bit (0x20).
Content is the concatenated encoding of all child elements.
Returns complete TLV (tag-length-value) encoded byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L102-L115)
---
## der-sequence
```clojure
(der-sequence & parts)
```
Encode DER SEQUENCE from parts.
Concatenates all parts and wraps in SEQUENCE tag (0x30).
Returns encoded SEQUENCE byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L119-L126)
---
## der-set
```clojure
(der-set & parts)
```
Encode DER SET from parts.
DER requires SET elements to be sorted in lexicographic order by their
encoded bytes. This implementation sorts all parts before encoding.
Returns encoded SET byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L128-L151)
---
## der-integer
```clojure
(der-integer value)
```
Encode DER INTEGER from long value.
Handles zero specially, uses BigInteger for proper two’s complement encoding.
Returns encoded INTEGER byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L153-L162)
---
## der-integer-bytes
```clojure
(der-integer-bytes value)
```
Encode DER INTEGER from byte array.
Adds leading zero if high bit is set to avoid negative interpretation.
Returns encoded INTEGER byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L164-L175)
---
## der-boolean
```clojure
(der-boolean v)
```
Encode DER BOOLEAN.
True encodes as 0xFF, false as 0x00.
Returns encoded BOOLEAN byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L177-L183)
---
## encode-base128
```clojure
(encode-base128 value)
```
Encode a long value as base-128 (for OID encoding).
Used by der-oid for encoding OID arc values.
Each byte has high bit set except the last byte.
Returns base-128 encoded bytes.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L185-L202)
---
## der-oid
```clojure
(der-oid dotted)
```
Encode DER OBJECT IDENTIFIER from dotted string.
Example: "2.5.4.3" for CN (Common Name).
First two arcs are encoded specially per X.690 (40*arc1 + arc2).
Returns encoded OID byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L204-L223)
---
## der-utf8-string
```clojure
(der-utf8-string s)
```
Encode DER UTF8String.
Encodes string using UTF-8 character set.
Returns encoded UTF8String byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L225-L231)
---
## der-octet-string
```clojure
(der-octet-string content)
```
Encode DER OCTET STRING.
Wraps arbitrary byte content in OCTET STRING tag (0x04).
Returns encoded OCTET STRING byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L233-L239)
---
## der-bit-string
```clojure
(der-bit-string bytes)
```
Encode DER BIT STRING.
First content byte specifies number of unused bits (always 0 here).
Returns encoded BIT STRING byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L241-L250)
---
## der-utc-time
```clojure
(der-utc-time date)
```
Encode DER UTCTime from Date.
Format: YYMMDDHHmmssZ (2-digit year, UTC timezone).
Used for dates before 2050 per X.509 conventions.
Returns encoded UTCTime byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L254-L263)
---
## der-generalized-time
```clojure
(der-generalized-time date)
```
Encode DER GeneralizedTime from Date.
Format: YYYYMMDDHHmmssZ (4-digit year, UTC timezone).
Used for dates in 2050 or later per X.509 conventions.
Returns encoded GeneralizedTime byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L265-L274)
---
## der-context-specific-constructed-implicit
```clojure
(der-context-specific-constructed-implicit tag-number content)
```
Encode IMPLICIT [tag] CONSTRUCTED with given content.
Context-specific tag with constructed bit set (0xA0 | tag-number).
Used for IMPLICIT tagging in ASN.1.
Returns encoded context-specific byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L278-L286)
---
## der-context-specific-primitive
```clojure
(der-context-specific-primitive tag-number raw-content)
```
Encode context-specific PRIMITIVE tag with raw content.
Context-specific tag without constructed bit (0x80 | tag-number).
Used for IMPLICIT tagging of primitive types in ASN.1.
Returns encoded context-specific byte array.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L288-L296)
---
## read-length
```clojure
(read-length data offset)
```
Read DER length and return [length bytes-consumed].
Supports short form (single byte < 128) and long form (multi-byte).
Returns vector of [length-value number-of-bytes-consumed].
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L300-L320)
---
## read-tlv
```clojure
(read-tlv data offset)
```
Read a complete TLV (tag-length-value) structure.
Returns a map with:
- `:tag` - the raw tag byte
- `:tag-class` - :universal, :application, :context-specific, or :private
- `:constructed?` - true if constructed (contains other TLVs)
- `:tag-number` - the tag number within the class
- `:value` - byte array of the value
- `:total-length` - total bytes consumed including tag and length
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L322-L354)
---
## decode-sequence-elements
```clojure
(decode-sequence-elements data)
```
Decode all elements in a SEQUENCE/SET value.
Takes the value bytes (not including SEQUENCE tag/length) and returns
a vector of TLV maps.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L356-L368)
---
## decode-integer
```clojure
(decode-integer value)
```
Decode a DER INTEGER value bytes to BigInteger.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L372-L375)
---
## decode-enumerated
```clojure
(decode-enumerated value)
```
Decode a DER ENUMERATED value bytes to long.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L377-L380)
---
## decode-oid
```clojure
(decode-oid value)
```
Decode a DER OID value bytes to dotted string notation.
First two arcs are encoded as (40 * arc1 + arc2) in the first byte(s).
Subsequent arcs use base-128 encoding with high bit continuation.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L382-L412)
---
## decode-octet-string
```clojure
(decode-octet-string value)
```
Return the raw bytes from an OCTET STRING value.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L414-L417)
---
## decode-bit-string
```clojure
(decode-bit-string value)
```
Decode a BIT STRING value, returning the actual bits as bytes.
First byte indicates unused bits in the last byte.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L419-L430)
---
## decode-ia5-string
```clojure
(decode-ia5-string value)
```
Decode an IA5String (ASCII) value to String.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L432-L435)
---
## decode-utf8-string
```clojure
(decode-utf8-string value)
```
Decode a UTF8String value to String.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L437-L440)
---
## decode-printable-string
```clojure
(decode-printable-string value)
```
Decode a PrintableString value to String.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L442-L445)
---
## decode-generalized-time
```clojure
(decode-generalized-time value)
```
Decode a GeneralizedTime value to java.time.Instant.
Format: YYYYMMDDHHmmss[.fraction]Z
Supports optional fractional seconds.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L458-L469)
---
## decode-utc-time
```clojure
(decode-utc-time value)
```
Decode a UTCTime value to java.time.Instant.
Format: YYMMDDHHmmssZ (2-digit year, interpreted as 1950-2049).
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L480-L490)
---
## find-context-tag
```clojure
(find-context-tag elements tag-number)
```
Find a context-specific tagged element by tag number in a sequence of TLVs.
Returns the TLV map if found, nil otherwise.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L494-L501)
---
## unwrap-octet-string
```clojure
(unwrap-octet-string data)
```
Unwrap a DER OCTET STRING and return its content bytes.
Parses the TLV structure and extracts the value.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L503-L511)
---
## unwrap-sequence
```clojure
(unwrap-sequence data)
```
Unwrap a DER SEQUENCE and return its decoded elements.
Parses the TLV structure and decodes all child elements.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/der.clj#L513-L521)
## ol.clave.crypto.impl.json
# ol.clave.crypto.impl.json
## read-str
```clojure
(read-str s)
(read-str s opts)
```
Returns a Clojure value from the JSON string.
Options:
:key-fn - Convert JSON keys using this function. Defaults to keyword.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/json.clj#L5-L12)
---
## write-str
```clojure
(write-str x)
(write-str x opts)
```
Returns a JSON string from the Clojure value.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/json.clj#L14-L18)
## ol.clave.crypto.impl.jwk
# ol.clave.crypto.impl.jwk
## key-algorithm
```clojure
(key-algorithm key)
```
Return :ol.clave.algo/es256 or :ol.clave.algo/ed25519 for supported keys.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jwk.clj#L31-L38)
---
## public-jwk
Return the public key as a JWK map.
| | |
| --- | --- |
| Key Type | JWK kty |
| EC | "EC" |
| Ed25519 | "OKP" |
| RSA | "RSA" |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jwk.clj#L59-L67)
---
## jwk->canonical-json
```clojure
(jwk->canonical-json jwk-map)
```
Render a public JWK map as canonical JSON for JWS embedding.
Fields are sorted alphabetically per RFC 7638.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jwk.clj#L98-L119)
---
## jwk-thumbprint-from-jwk
```clojure
(jwk-thumbprint-from-jwk jwk-map)
```
Compute RFC 7638 thumbprint from a JWK map.
Returns base64url-encoded SHA-256 hash of canonical JWK.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jwk.clj#L121-L128)
---
## jwk-thumbprint
```clojure
(jwk-thumbprint public-key)
```
Compute RFC 7638 thumbprint for a public key.
Returns base64url-encoded SHA-256 hash of canonical JWK.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jwk.clj#L130-L135)
## ol.clave.crypto.impl.jws
# ol.clave.crypto.impl.jws
## jws-alg
Return the JWS `alg` header value for a key.
| | |
| --- | --- |
| Key Type | Result |
| P-256 | "ES256" |
| P-384 | "ES384" |
| Ed25519 | "EdDSA" |
| RSA | "RS256" |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L123-L132)
---
## sign
Sign data bytes, return signature bytes in JWS format.
For ECDSA, returns R||S concatenated (not DER).
For EdDSA and RSA, returns raw signature bytes.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L148-L153)
---
## protected-header-json
```clojure
(protected-header-json alg kid nonce url jwk-json)
```
Construct the protected header JSON string with deterministic field order.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L194-L218)
---
## final-jws-json
```clojure
(final-jws-json protected-b64 payload-b64 signature-b64)
```
Assemble the final JWS JSON object with deterministic ordering.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L220-L225)
---
## protected-dot-payload-bytes
```clojure
(protected-dot-payload-bytes protected-b64 payload-b64)
```
Return ASCII bytes of '<protected>.<payload>'.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L227-L230)
---
## encode-payload-b64
```clojure
(encode-payload-b64 payload-json)
```
Base64url-encode the payload JSON string or return the empty string when nil.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L232-L239)
---
## encode-protected-b64
```clojure
(encode-protected-b64 alg kid nonce url jwk-json)
```
Construct and base64url-encode the protected header JSON.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L241-L245)
---
## encode-signature-b64
```clojure
(encode-signature-b64 alg private-key-or-mac protected-dot-payload)
```
Compute the signature for the given alg and return base64url-encoded value.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L247-L256)
---
## jws-encode-json
```clojure
(jws-encode-json payload-json keypair kid nonce url)
```
Build a JSON-serialized JWS object.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L281-L291)
---
## jws-encode-eab
```clojure
(jws-encode-eab account-key-or-keypair mac-key kid url)
```
Construct an External Account Binding JWS per RFC 8555 section 7.3.4.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/jws.clj#L293-L306)
## ol.clave.crypto.impl.parse-ip
# ol.clave.crypto.impl.parse-ip
Parse a string representation of an IPv4 or IPv6 address into an InetAddress instance.
Unlike `InetAddress/getByName`, the functions in this namespace never cause
DNS services to be accessed. This avoids potentially blocking/side-effecting
network calls that can occur when using the JDK’s built-in methods to parse IP
addresses.
This implementation is inspired by Google Guava’s InetAddresses class, which
provides similar functionality in Java. This code focuses on strict validation
of IP address formats according to relevant RFCs.
Features:
- Non-blocking IP address parsing with pure functions
- Strict RFC-compliant validation
- Support for all IPv4 formats
- Support for all IPv6 formats (including compressed notation and embedded IPv4)
- Support for IPv6 scope identifiers
The main entry point is `parse-ip`, which takes a string and returns an
InetAddress instance or nil if the input is an invalid ip address literal.
## ip-string->bytes
```clojure
(ip-string->bytes ip-string)
```
Convert an IP string to bytes. Returns [byte-array scope-id] or nil if invalid.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/parse_ip.clj#L204-L225)
---
## bytes->inet-address
```clojure
(bytes->inet-address addr)
(bytes->inet-address addr scope)
```
Convert a byte array into an InetAddress.
Args:
addr: the raw 4-byte or 16-byte IP address in big-endian order
scope: optional scope identifier for IPv6 addresses
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/parse_ip.clj#L235-L260)
---
## from-string
```clojure
(from-string ip-string)
```
Returns the InetAddress having the given string representation or nil otherwise.
This function parses IP address strings without performing DNS lookups, making it
suitable for environments where DNS lookups would cause unwanted blocking or side effects.
It supports:
- IPv4 addresses in dotted decimal format (e.g., "192.168.1.1")
- IPv6 addresses in hex format with optional compression (e.g., "2001:db8::1")
- IPv6 addresses with scope IDs (e.g., "fe80::1%eth0" or "fe80::1%1")
- IPv6 addresses with embedded IPv4 (e.g., "::ffff:192.168.1.1")
If the input is already an InetAddress, it is returned unchanged.
Args:
ip-string: A string representing an IP address or an InetAddress
Returns:
An InetAddress object, or nil if the input couldn’t be parsed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/parse_ip.clj#L262-L294)
## ol.clave.crypto.impl.util
# ol.clave.crypto.impl.util
## qualify-keys
```clojure
(qualify-keys ns-prefix m)
```
Qualify the top-level keys of map `m` into `ns-prefix`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/crypto/impl/util.clj#L5-L17)
## ol.clave.errors
# ol.clave.errors
Shared error keyword definitions and helpers for ex-info payloads.
## unsupported-key
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L4-L4)
---
## invalid-header
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L5-L5)
---
## invalid-eab
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L6-L6)
---
## signing-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L7-L7)
---
## ecdsa-signature-format
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L8-L8)
---
## json-escape
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L9-L9)
---
## base64
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L10-L10)
---
## invalid-account-edn
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L12-L12)
---
## invalid-account
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L13-L13)
---
## invalid-contact
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L14-L14)
---
## invalid-contact-entry
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L15-L15)
---
## invalid-contact-uri
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L16-L16)
---
## invalid-tos
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L17-L17)
---
## invalid-directory
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L19-L19)
---
## account-creation-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L21-L21)
---
## missing-location-header
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L22-L22)
---
## cancelled
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L24-L24)
---
## timeout
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L25-L25)
---
## invalid-scope
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L26-L26)
---
## account-retrieval-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L29-L29)
---
## account-update-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L30-L30)
---
## account-deactivation-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L31-L31)
---
## external-account-required
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L32-L32)
---
## unauthorized-account
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L33-L33)
---
## missing-account-context
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L34-L34)
---
## invalid-account-key
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L35-L35)
---
## account-key-rollover-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L36-L36)
---
## account-key-rollover-verification-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L37-L37)
---
## account-not-found
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L38-L38)
---
## invalid-san
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L41-L41)
---
## invalid-idna
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L42-L42)
---
## invalid-ip
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L43-L43)
---
## encoding-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L44-L44)
---
## unsupported-identifier
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L47-L47)
---
## malformed-pem
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L49-L49)
---
## key-mismatch
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L50-L50)
---
## problem
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L53-L53)
---
## server-error
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L55-L55)
---
## value-too-large
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L57-L57)
---
## order-creation-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L60-L60)
---
## order-retrieval-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L61-L61)
---
## order-not-ready
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L62-L62)
---
## order-invalid
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L63-L63)
---
## order-timeout
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L64-L64)
---
## order-inconsistent
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L65-L65)
---
## authorization-retrieval-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L68-L68)
---
## authorization-invalid
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L69-L69)
---
## authorization-unusable
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L70-L70)
---
## authorization-timeout
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L71-L71)
---
## challenge-rejected
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L72-L72)
---
## no-compatible-challenge
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L73-L73)
---
## invalid-solver
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L76-L76)
---
## pre-authorization-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L79-L79)
---
## pre-authorization-unsupported
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L80-L80)
---
## wildcard-identifier-not-allowed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L81-L81)
---
## certificate-download-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L84-L84)
---
## unexpected-content-type
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L85-L85)
---
## revocation-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L88-L88)
---
## invalid-certificate
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L89-L89)
---
## renewal-info-failed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L92-L92)
---
## renewal-info-invalid
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L93-L93)
---
## problem-type-ns
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L101-L101)
---
## pt-account-does-not-exist
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L103-L103)
---
## pt-already-revoked
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L104-L104)
---
## pt-bad-csr
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L105-L105)
---
## pt-bad-nonce
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L106-L106)
---
## pt-bad-public-key
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L107-L107)
---
## pt-bad-revocation-reason
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L108-L108)
---
## pt-bad-signature-algorithm
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L109-L109)
---
## pt-caa
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L110-L110)
---
## pt-compound
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L111-L111)
---
## pt-connection
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L112-L112)
---
## pt-dns
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L113-L113)
---
## pt-external-account-required
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L114-L114)
---
## pt-incorrect-response
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L115-L115)
---
## pt-invalid-contact
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L116-L116)
---
## pt-malformed
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L117-L117)
---
## pt-order-not-ready
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L118-L118)
---
## pt-rate-limited
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L119-L119)
---
## pt-rejected-identifier
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L120-L120)
---
## pt-server-internal
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L121-L121)
---
## pt-tls
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L122-L122)
---
## pt-unauthorized
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L123-L123)
---
## pt-unsupported-contact
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L124-L124)
---
## pt-unsupported-identifier
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L125-L125)
---
## pt-user-action-required
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L126-L126)
---
## pt-already-replaced
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L128-L128)
---
## failed-identifiers
```clojure
(failed-identifiers problem)
```
Extract identifiers from problem subproblems.
Returns vector of identifier maps, e.g., `[{:type "dns" :value "example.com"}]`.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L134-L138)
---
## subproblem-for
```clojure
(subproblem-for problem identifier)
```
Find subproblem for specific identifier.
Returns the first subproblem matching the given identifier, or nil.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L140-L144)
---
## ex
```clojure
(ex type message data)
(ex type message data cause)
```
Convenience wrapper for ex-info that associates the shared :type key.
Usage: (errors/ex errors/invalid-header "message" {:field :kid :reason "missing"})
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/errors.clj#L146-L152)
## ol.clave.ext.common
# ol.clave.ext.common
Common utilities for clave server extensions.
This namespace provides server-agnostic helpers for working with
clave’s automation layer, including keystore creation and event processing.
These functions can be used by any server extension (Jetty, http-kit, etc.).
## create-keystore
```clojure
(create-keystore bundle)
(create-keystore bundle password)
```
Create an in-memory PKCS12 KeyStore from a clave certificate bundle.
No disk I/O - purely in-memory operation suitable for TLS handshakes.
| | |
| --- | --- |
| key | description |
| `bundle` | Certificate bundle from [`ol.clave.automation/lookup-cert`](api/ol-clave-automation.adoc#lookup-cert) |
| `password` | Optional keystore password (default "changeit") |
Returns a `java.security.KeyStore` ready for use with TLS servers.
Returns nil if bundle is nil (no certificate available yet).
```clojure
(create-keystore (auto/lookup-cert system "example.com"))
;; => #object[java.security.KeyStore ...]
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L15-L44)
---
## certificate-event?
```clojure
(certificate-event? evt)
```
Check if an event indicates a certificate change.
Returns true for `:certificate-obtained`, `:certificate-renewed`, and
`:certificate-loaded` events.
| | |
| --- | --- |
| key | description |
| `evt` | Event from [`ol.clave.automation/get-event-queue`](api/ol-clave-automation.adoc#get-event-queue) |
```clojure
(when (certificate-event? evt)
(log/info "Certificate updated for" (event-domain evt)))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L46-L62)
---
## event-domain
```clojure
(event-domain evt)
```
Extract the domain name from a certificate event.
Returns the domain string or nil if event has no domain.
| | |
| --- | --- |
| key | description |
| `evt` | Event map |
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L64-L73)
---
## wrap-redirect-https
```clojure
(wrap-redirect-https handler)
(wrap-redirect-https handler {:keys [ssl-port] :or {ssl-port 443}})
```
Ring middleware that redirects HTTP requests to HTTPS.
| | |
| --- | --- |
| key | description |
| `handler` | Ring handler to wrap |
| `opts` | Options map with `:ssl-port` |
Options:
- `:ssl-port` - HTTPS port for redirect URL.
Defaults to 443 (implicit, no port in URL).
Use a custom port like 8443 to include it explicitly.
Passes through requests that are already HTTPS (by `:scheme` or `x-forwarded-proto` header).
```clojure
(wrap-redirect-https handler {:ssl-port 8443})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L75-L106)
---
## no-op-solver
```clojure
(no-op-solver)
```
Create a no-op ACME solver for testing.
Returns a solver that does nothing.
Useful with `PEBBLE_VA_ALWAYS_VALID=1` where challenge validation is skipped.
```clojure
{:solvers {:http-01 (no-op-solver)}}
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L108-L119)
---
## wait-for-certificates
```clojure
(wait-for-certificates system domains)
```
Wait for certificates to be available for all domains.
Polls [`ol.clave.automation/lookup-cert`](api/ol-clave-automation.adoc#lookup-cert) once per second until certificates
are available for every domain. Blocks indefinitely until the automation
system obtains all certificates or throws an error.
| | |
| --- | --- |
| key | description |
| `system` | clave automation system |
| `domains` | Vector of domains to wait for |
Returns nil once all certificates are available.
```clojure
(wait-for-certificate system ["example.com" "www.example.com"])
(create-keystore (auto/lookup-cert system "example.com"))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/common.clj#L121-L146)
## ol.clave.ext.jetty
# ol.clave.ext.jetty
Jetty integration for clave automation.
Provides SNI-based certificate selection during TLS handshakes.
Certificates are looked up on-demand for each connection based on the
requested hostname, so renewals take effect immediately.
This ns is useful for authors integrating with jetty directly. If you want to use a jetty ring adapter see:
* [`ol.clave.ext.ring-jetty-adapter`](api/ol-clave-ext-ring-jetty-adapter.adoc)
## sni-key-manager
```clojure
(sni-key-manager lookup-fn)
```
Create an X509ExtendedKeyManager that looks up certificates by SNI hostname.
On each TLS handshake, extracts the SNI hostname from the ClientHello and
calls `lookup-fn` to retrieve the certificate bundle for that hostname.
| | |
| --- | --- |
| key | description |
| `lookup-fn` | Function `(fn [hostname] bundle)` returning cert bundle |
The `lookup-fn` receives the SNI hostname and should return a certificate
bundle map with `:certificate` (vector of X509Certificate) and `:private-key`.
Returns nil if no certificate is available for that hostname.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/jetty.clj#L34-L85)
---
## sni-ssl-context
```clojure
(sni-ssl-context lookup-fn)
```
Create an SSLContext configured with SNI-aware certificate selection.
Uses `lookup-fn` to fetch certificates during TLS handshake based on
the client’s requested hostname (SNI). Certificates are looked up fresh
on each handshake, so renewals take effect immediately.
| | |
| --- | --- |
| key | description |
| `lookup-fn` | Function `(fn [hostname] bundle)` returning cert bundle |
Returns a `javax.net.ssl.SSLContext` ready for use with Jetty.
```clojure
(def ssl-ctx (sni-ssl-context
(fn [hostname] (auto/lookup-cert system hostname))))
(jetty/run-jetty handler {:ssl-context ssl-ctx ...})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/jetty.clj#L87-L112)
---
## sni-alpn-key-manager
```clojure
(sni-alpn-key-manager lookup-fn challenge-registry)
```
Create an X509ExtendedKeyManager that handles both SNI and ALPN challenges.
Extends the SNI-based certificate selection with TLS-ALPN-01 challenge support.
During TLS handshake:
1. If ALPN protocol is 'acme-tls/1' and challenge-registry has data, serve challenge cert
2. Otherwise, use lookup-fn for normal SNI-based cert selection
This does NOT interfere with HTTP/2 (h2) negotiation because:
- Regular clients offer ["h2", "http/1.1"] -> normal cert via SNI lookup
- ACME servers offer ["acme-tls/1"] exclusively -> challenge cert
| | |
| --- | --- |
| key | description |
| `lookup-fn` | Function `(fn [hostname] bundle)` for SNI lookup |
| `challenge-registry` | Atom with domain->challenge-cert-data map |
The challenge-registry contains maps as returned by `tlsalpn01-challenge-cert`:
- `:x509` - The X509Certificate to serve
- `:keypair` - The KeyPair with private key
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/jetty.clj#L124-L202)
---
## sni-alpn-ssl-context
```clojure
(sni-alpn-ssl-context lookup-fn tls-alpn-solver)
```
Create an SSLContext with SNI cert selection and ALPN challenge support.
For normal HTTPS traffic: looks up certs by SNI hostname via lookup-fn.
For ACME TLS-ALPN-01 challenges: serves challenge cert when ALPN is 'acme-tls/1'.
| | |
| --- | --- |
| key | description |
| `lookup-fn` | Function `(fn [hostname] bundle)` for SNI lookup |
| `tls-alpn-solver` | TLS-ALPN solver with `:registry` key ) |
Returns a `javax.net.ssl.SSLContext` ready for use with Jetty.
```clojure
(def tls-alpn-solver (tls-alpn/switchable-solver {:port 443}))
(def ssl-ctx (sni-alpn-ssl-context
(fn [hostname] (auto/lookup-cert system hostname))
tls-alpn-solver))
(jetty/run-jetty handler {:ssl-context ssl-ctx ...})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/jetty.clj#L204-L231)
## ol.clave.ext.ring-jetty-adapter
# ol.clave.ext.ring-jetty-adapter
Ring Jetty adapter integration for clave automation.
Provides a high-level API for running Jetty with auto-renewing TLS certificates.
Wraps ring-jetty-adapter with the same `[handler opts]` signature.
Automatically configures both HTTP-01 and TLS-ALPN-01 solvers.
Uses SNI-based certificate selection: certificates are looked up fresh on each
TLS handshake, so renewals take effect immediately without server restart.
```clojure
(require '[ol.clave.ext.ring-jetty-adapter :as clave-jetty])
(def ctx (clave-jetty/run-jetty handler
{:port 80
:ssl-port 443
::clave-jetty/config
{:storage (file-storage/file-storage {:root "/tmp/certs"})
:issuers [{:directory-url "https://acme-v02.api.letsencrypt.org/directory"
:email "admin@example.com"}]
:domains ["example.com"]}}))
;; Later: stop everything
(clave-jetty/stop ctx)
```
## run-jetty
```clojure
(run-jetty handler {::keys [config] :as opts})
```
Serve `handler` over HTTPS for all `domains` with automatic certificate management.
This is an opinionated, high-level convenience function that applies sane
defaults for production use: challenge solving, HTTP to HTTPS redirects,
and SNI-based certificate selection.
Blocks until the initial certificate is obtained, then starts serving.
Redirects all HTTP requests to HTTPS (when HTTP port is configured).
Obtains and renews TLS certificates automatically.
Certificate renewals take effect immediately via SNI-based selection.
For advanced use cases, use `ring.adapter.jetty/run-jetty` directly with
[`ol.clave.ext.jetty`](api/ol-clave-ext-jetty.adoc) functions for certificate management.
`opts` are passed through to `ring.adapter.jetty/run-jetty`.
Exception: the `:join?` option from `ring.adapter.jetty/run-jetty` is not supported.
Use [`stop`](#stop) to shut down the server instead.
Calling this function signifies acceptance of the CA’s Subscriber Agreement
and/or Terms of Service.
| | |
| --- | --- |
| key | description |
| `handler` | Ring handler |
| `opts` | Options map, see `ring.adapter.jetty/run-jetty` for jetty-adapter’s options |
Clave config is provided via the `:ol.clave.ext.ring-jetty-adapter/config` key in `opts`:
| | | |
| --- | --- | --- |
| key | description | default |
| `:domains` | Domains to manage certs for (required) | |
| `:redirect-http?` | Wrap handler with HTTP->HTTPS redirect | true |
Additional automation config keys (e.g., `:issuers`, `:storage` etc.) are passed through to [`ol.clave.automation/create`](api/ol-clave-automation.adoc#create).
Returns a context map for use with [`stop`](#stop).
```clojure
(def server (run-jetty handler
{:port 80 :ssl-port 443
::config {:domains ["example.com"]}}))
;; Later:
(stop server)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/ring_jetty_adapter.clj#L45-L119)
---
## stop
```clojure
(stop {:keys [server system]})
```
Stop a server context returned by [`run-jetty`](#run-jetty).
Stops the Jetty server and automation system.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/ext/ring_jetty_adapter.clj#L121-L127)
## ol.clave.issuers
# ol.clave.issuers
Convenience ACME directory URLs for well-known certificate authorities.
Check each provider’s documentation before use. Some providers require
External Account Binding (EAB), account setup, or payment.
Compatibility note: these URLs point to external services and may change or
disappear without a major version bump.
## lets-encrypt-staging-ca
Let’s Encrypt staging ACME directory URL.
See https://letsencrypt.org/docs/staging-environment/.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/issuers.clj#L10-L14)
---
## lets-encrypt-production-ca
Let’s Encrypt production ACME directory URL.
See https://letsencrypt.org/getting-started/.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/issuers.clj#L16-L20)
---
## zerossl-production-ca
ZeroSSL production ACME directory URL.
See https://zerossl.com/documentation/acme/.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/issuers.clj#L22-L26)
---
## google-trust-staging-ca
Google Trust Services staging ACME directory URL.
See https://cloud.google.com/certificate-manager/docs/public-ca-tutorial.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/issuers.clj#L28-L32)
---
## google-trust-production-ca
Google Trust Services production ACME directory URL.
See https://cloud.google.com/certificate-manager/docs/public-ca-tutorial.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/issuers.clj#L34-L38)
## ol.clave.lease
# ol.clave.lease
Cooperative cancellation and deadline propagation for concurrent operations.
A `lease` represents a bounded lifetime for work.
It carries an optional deadline, tracks cancellation state, and provides a
completion signal that callers can poll or block on.
## Motivation
Individual timeout parameters scattered across function calls lead to
inconsistent budgets and subtle hangs.
A `lease` provides one coherent boundary: the caller defines the maximum time
for an entire operation, and all nested work inherits that constraint.
The `lease` is intentionally minimal and does not prescribe any particulary
concurrency model. Virtual threads, core.async go-loops, Java
StructuredTaskScopes, etc all work equally well with leases.
This design enables leases to serve as common ground between libraries with
different runtime preferences. A library using one concurrency model can
accept the same lease type as a library using another, allowing callers to
compose them without adapter layers or runtime coupling.
## Cooperative Cancellation
Cancellation are entirely advisory.
A `lease` does not forcibly terminate anything. It merely records that
cancellation has been requested and notifies interested parties.
The lease transitions from `:active` to `:cancelled` or `:timed-out`, but
running code continues unless it explicitly checks the lease and decides to
stop.
This cooperative model requires discipline from functions that receive a
lease: they must periodically consult the lease and honor cancellation
promptly.
The tradeoff is predictability.
Code always controls its own teardown, resources are released in an orderly
fashion, and there are no surprising interruptions mid-operation.
## Concepts
Lease: A value representing cancellation state (`:active`, `:cancelled`, or
`:timed-out`), an optional deadline (monotonic nanoTime), and parent-child
relationships for propagation.
Deadline vs timeout: A timeout is a duration ("at most 2 seconds"); a
deadline is an absolute monotonic bound ("stop at nanoTime T").
This library accepts either via [`with-timeout`](#with-timeout) and [`with-deadline`](#with-deadline), but
internally tracks a single deadline per lease.
Deadlines use `System/nanoTime` for monotonic timing, immune to wall-clock
changes (NTP adjustments, manual clock changes, VM suspend/resume).
## Honoring a Lease
When your function receives a lease as an argument, you are accepting
responsibility to respect it.
The caller trusts that if they cancel the lease, your function will notice
and stop work promptly and correctly.
At a minimum, check the lease before starting expensive operations.
Call [`ensure-active`](#ensure-active) at the top of your function and at natural checkpoints: the
start of each loop iteration, before issuing a network request, or after
returning from a potentially slow sub-call.
If the lease has been cancelled, [`ensure-active`](#ensure-active) throws an exception containing
the cancellation cause, which unwinds the stack cleanly.
For non-throwing checks, use [`active?`](#active?) and return early or break out of
loops when it returns false.
The choice between throwing and returning depends on your error-handling
style, but the principle is the same: stop doing new work once the lease is
no longer active.
When calling other lease-aware functions, pass the lease through so they
inherit the same cancellation boundary.
If you need to spawn concurrent work, derive child leases with [`with-cancel`](#with-cancel)
or [`with-timeout`](#with-timeout) and cancel them in a finally block to ensure cleanup.
The goal is prompt, graceful termination without leaking resources.
A well-behaved function notices cancellation within a reasonable window
(tens to hundreds of milliseconds for most operations) and exits without
leaving resources dangling or work half-done.
## Parent-Child Relationships
Derived leases form a tree.
When a parent cancels, all descendants cancel automatically.
Children never cancel parents.
Child deadlines adopt the earliest of the parent deadline and any explicitly
provided deadline.
## Thread Safety
Leases are safe for concurrent use.
Multiple threads may read state, register listeners, or attempt cancellation.
Only the first cancellation wins; subsequent calls are no-ops.
## Usage
```clojure
(require '[ol.clave.lease :as l])
;; Create a root lease and derive a child with timeout
(let [[lease cancel] (l/with-timeout (l/background) 5000)]
(try
(do-work lease)
(finally
(cancel))))
;; Check cancellation state
(when (l/active? lease)
(continue-work))
;; Block until cancelled
(deref (l/done-signal lease))
```
See also: [`with-cancel`](#with-cancel), [`with-timeout`](#with-timeout), [`with-deadline`](#with-deadline), [`ensure-active`](#ensure-active)
## ILease
Protocol for cooperative cancellation and deadline tracking.
All methods are non-blocking and safe for concurrent use from multiple
threads.
The [`done-signal`](#done-signal) method returns a derefable; blocking occurs only when
dereferencing that signal.
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L125-L162)
### deadline
```clojure
(deadline lease)
```
Returns the deadline as a monotonic nanoTime (Long), or `nil` if no deadline.
The value is from `System/nanoTime` and is only meaningful for comparison
with other nanoTime values.
Use <> to get a human-readable `Duration` until expiry.
---
### done-signal
```clojure
(done-signal lease)
```
Returns a read-only derefable that yields `true` when `lease` ends.
Use `deref` with a timeout to wait for cancellation, or
`clojure.core/realized?` for a non-blocking check.
[source,clojure]
```
;; Block with timeout
(deref (done-signal lease) 1000 :still-active)
;; Non-blocking check
(realized? (done-signal lease))
```
---
### cause
```clojure
(cause lease)
```
Returns the `Throwable` that caused cancellation, or `nil` if `lease` is active.
The cause contains `:type` in its `ex-data`:
- `:lease/cancelled` for explicit cancellation
- `:lease/deadline-exceeded` for timeout
---
### active?
```clojure
(active? lease)
```
Returns `true` when `lease` has not been cancelled or timed out.
---
## background
```clojure
(background)
```
Creates a root lease with no deadline or parent.
The background lease is never cancelled on its own; it serves as the ancestor
for all derived leases in an operation tree.
Use [`with-cancel`](#with-cancel), [`with-timeout`](#with-timeout), or [`with-deadline`](#with-deadline) to derive child
leases with cancellation or deadline constraints.
```clojure
(let [root (background)
[child cancel] (with-timeout root 5000)]
(try
(do-work child)
(finally
(cancel))))
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L335-L352)
---
## with-cancel
```clojure
(with-cancel parent)
```
Derives a cancellable child lease from `parent`.
Returns `[child cancel-fn]` where:
- `child` is the derived lease, inheriting `parent’s deadline
- `cancel-fn` cancels `child` and all its descendants
The cancel function accepts an optional cause argument.
Without arguments, it uses a generic `:lease/cancelled` exception.
Calling cancel multiple times is safe; only the first call takes effect.
When `parent` cancels, `child` cancels automatically with the same cause.
```clojure
(let [[lease cancel] (with-cancel parent)]
(try
(do-work lease)
(finally
(cancel))))
;; Cancel with custom cause
(cancel (ex-info "user abort" {:reason :user-request}))
```
See also: [`with-timeout`](#with-timeout), [`with-deadline`](#with-deadline), [`background`](#background)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L354-L388)
---
## with-deadline
```clojure
(with-deadline parent dl)
```
Derives a child lease from `parent` with an absolute monotonic deadline.
Returns `[child cancel-fn]` where `child` will automatically cancel with
`:lease/deadline-exceeded` when the deadline passes.
`dl` is a monotonic nanoTime value from `System/nanoTime`.
The effective deadline is the earlier of `parent’s deadline and `dl`.
If `dl` has already passed, `child` is cancelled immediately.
Most callers should prefer [`with-timeout`](#with-timeout) which accepts human-friendly
Duration or milliseconds.
```clojure
;; 30 second deadline using nanoTime
(let [dl (+ (System/nanoTime) (* 30 1000000000))
[lease cancel] (l/with-deadline parent dl)]
(try
(do-work lease)
(finally
(cancel))))
```
See also: [`with-timeout`](#with-timeout), [`with-cancel`](#with-cancel), [`deadline`](#deadline)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L390-L424)
---
## with-timeout
```clojure
(with-timeout parent timeout)
```
Derives a child lease from `parent` with a relative timeout.
Returns `[child cancel-fn]` where `child` will automatically cancel with
`:lease/deadline-exceeded` after `timeout` elapses.
`timeout` may be:
- `java.time.Duration` for precise control
- Long/integer for milliseconds
The effective deadline is computed as `(now + timeout)` and combined with
the parent deadline using earliest-wins semantics.
A zero or negative timeout cancels the child immediately.
```clojure
;; 5 second timeout
(let [[lease cancel] (with-timeout parent 5000)]
(try
(do-work lease)
(finally
(cancel))))
;; Using Duration
(with-timeout parent (Duration/ofSeconds 30))
```
See also: [`with-deadline`](#with-deadline), [`with-cancel`](#with-cancel)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L426-L454)
---
## ensure-active
```clojure
(ensure-active lease)
```
Returns `lease` if active, otherwise throws the cancellation cause.
Use this for explicit cancellation checks that should fail fast.
The thrown exception is the same value returned by [`cause`](#cause), containing
`:type` of either `:lease/cancelled` or `:lease/deadline-exceeded`.
```clojure
;; Check and continue
(ensure-active lease)
(do-next-step)
;; In a loop
(loop []
(ensure-active lease)
(when (more-work?)
(process-item)
(recur)))
```
See also: [`active?`](#active?), [`cause`](#cause)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L456-L480)
---
## remaining
```clojure
(remaining lease)
```
Returns the time remaining until `lease` expires as a `java.time.Duration`.
Returns `nil` if the lease has no deadline.
Returns `Duration/ZERO` if the deadline has already passed.
```clojure
(when-let [dur (l/remaining lease)]
(println "Time left:" (.toMillis dur) "ms"))
```
See also: [`deadline`](#deadline), [`active?`](#active?)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L482-L499)
---
## sleep
```clojure
(sleep lease ms)
```
Cooperatively wait for `ms` milliseconds or until `lease` ends.
Returns `:slept` if the full duration elapsed, or `:lease-ended` if the
lease was cancelled or timed out during the wait.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/lease.clj#L501-L512)
## ol.clave.specs
# ol.clave.specs
## new-nonce-url
```clojure
(new-nonce-url session)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L91-L92)
---
## new-account-url
```clojure
(new-account-url session)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L94-L95)
---
## new-order-url
```clojure
(new-order-url session)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L97-L98)
---
## key-change-url
```clojure
(key-change-url session)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L100-L101)
---
## new-authz-url
```clojure
(new-authz-url session)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L103-L104)
---
## identifier?
```clojure
(identifier? value)
```
Return true when `value` conforms to an ACME identifier map.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L139-L142)
---
## order-url
```clojure
(order-url order)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L164-L165)
---
## certificate-url
```clojure
(certificate-url order)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/specs.clj#L167-L168)
## ol.clave.storage.file
# ol.clave.storage.file
Filesystem-based storage implementation.
Provides a [`FileStorage`](#filestorage) record that implements [`ol.clave.storage/Storage`](api/ol-clave-storage.adoc#Storage)
and [`ol.clave.storage/TryLocker`](api/ol-clave-storage.adoc#TryLocker) using the local filesystem.
The presence of a lock file for a given name indicates a lock is held.
## Key-to-Path Mapping
Storage keys map directly to filesystem paths relative to a root directory.
Key normalization converts backslashes to forward slashes and strips leading
and trailing slashes.
Path traversal attempts (keys containing `..` that escape the root) throw
`IllegalArgumentException`.
## Atomic Writes
All writes use atomic move semantics: data is written to a temporary file
and renamed into place.
This ensures readers never see partial writes.
On filesystems that do not support atomic moves, a best-effort rename is used.
## Locking
Advisory locking is implemented with lock files in a `locks/` subdirectory.
Locks are created atomically by relying on the filesystem to enforce
exclusive file creation.
Processes that terminate abnormally will not have a chance to clean up their
lock files.
To handle this, while a lock is actively held, a background virtual thread
periodically updates a timestamp in the lock file (every 5 seconds).
If another process tries to acquire the lock but fails, it checks whether
the timestamp is still fresh.
If so, it waits by polling (every 1 second).
Otherwise, the stale lock file is deleted, effectively forcing an unlock.
While lock acquisition is atomic, unlocking is not perfectly atomic.
Filesystems offer atomic file creation but not necessarily atomic deletion.
It is theoretically possible for two processes to discover the same stale
lock and both attempt to delete it.
If one process deletes the lock file and creates a new one before the other
calls delete, the new lock may be deleted by mistake.
This means mutual exclusion is not guaranteed to be perfectly enforced in
the presence of stale locks.
However, these cases are rare, and we prefer the simpler solution over
alternatives that risk infinite loops.
## Filesystem Considerations
This implementation is designed for local filesystems and relies on specific
filesystem semantics:
* Exclusive file creation via `O_CREAT | O_EXCL`.
Lock acquisition depends on the filesystem atomically failing when creating
a file that already exists.
* Durable writes via `fsync`.
Lock file timestamps must survive crashes to enable stale lock detection.
Network filesystems (NFS, CIFS/SMB, AWS EFS) may not reliably support these
semantics.
In particular, some network filesystems do not honor `O_EXCL` across nodes or
do not guarantee that data is persisted after `fsync`, which can leave lock
files empty or corrupt after a crash or network interruption.
## Permissions
On POSIX systems, files are created with `rw-------` and directories with
`rwx------`.
On non-POSIX systems (Windows), default permissions apply.
## Usage
```clojure
(require '[ol.clave.storage.file :as fs]
'[ol.clave.storage :as s])
;; Use platform-appropriate directories for certificate storage
(def storage (fs/file-storage {:root (fs/data-dir "myapp")}))
;; Or specify a custom path
(def storage (fs/file-storage {:root "/var/lib/myapp"}))
;; Store and retrieve data
(s/store-string storage nil "certs/example.com/cert.pem" cert)
(s/load-string storage nil "certs/example.com/cert.pem")
```
## Related Namespaces
* [`ol.clave.storage`](api/ol-clave-storage.adoc) - Storage protocol and utilities
* [`ol.clave.lease`](api/ol-clave-lease.adoc) - Cooperative cancellation
## ->LockMeta
```clojure
(->LockMeta created-ms updated-ms)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L225-L225)
---
## LockMeta
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L225-L225)
---
## map->LockMeta
```clojure
(map->LockMeta m)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L225-L225)
---
## ->FileStorage
```clojure
(->FileStorage root)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L357-L412)
---
## FileStorage
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L357-L412)
---
## map->FileStorage
```clojure
(map->FileStorage m)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L357-L412)
---
## home-dir
```clojure
(home-dir)
```
Returns the user’s home directory.
Uses the `user.home` system property, which the JVM resolves appropriately
for each platform.
Returns `nil` if the home directory cannot be determined.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L442-L449)
---
## state-dir
```clojure
(state-dir)
(state-dir app-name)
```
Returns the directory for persistent application state.
With no arguments, returns the base directory (caller appends app name).
With `app-name`, appends it as a subdirectory (unless systemd provides one).
Checks environment variables in order:
1. `$STATE_DIRECTORY` - set by systemd for system units (already app-specific)
2. `$XDG_STATE_HOME` - XDG base directory spec / systemd user units
3. Platform default
When systemd sets `$STATE_DIRECTORY`, it may contain multiple colon-separated
paths if the unit configures multiple directories; this function returns the
first path.
Platform defaults when no environment variable is set:
| | |
| --- | --- |
| platform | path |
| Linux | `$HOME/.local/state` |
| macOS | `$HOME/Library/Application Support` |
| Windows | `%LOCALAPPDATA%` |
Returns `nil` if a suitable directory cannot be determined.
```clojure
(state-dir) ; => "/home/user/.local/state"
(state-dir "myapp") ; => "/home/user/.local/state/myapp"
;; Under systemd system unit with StateDirectory=myapp:
(state-dir) ; => "/var/lib/myapp"
(state-dir "myapp") ; => "/var/lib/myapp" (no double append)
(file-storage {:root (state-dir "myapp")})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L451-L500)
---
## config-dir
```clojure
(config-dir)
(config-dir app-name)
```
Returns the directory for application configuration files.
With no arguments, returns the base directory (caller appends app name).
With `app-name`, appends it as a subdirectory (unless systemd provides one).
Checks environment variables in order:
1. `$CONFIGURATION_DIRECTORY` - set by systemd for system units (already app-specific)
2. `$XDG_CONFIG_HOME` - XDG base directory spec / systemd user units
3. Platform default
When systemd sets `$CONFIGURATION_DIRECTORY`, it may contain multiple
colon-separated paths if the unit configures multiple directories; this
function returns the first path.
Platform defaults when no environment variable is set:
| | |
| --- | --- |
| platform | path |
| Linux | `$HOME/.config` |
| macOS | `$HOME/Library/Preferences` |
| Windows | `%APPDATA%` |
Returns `nil` if a suitable directory cannot be determined.
```clojure
(config-dir) ; => "/home/user/.config"
(config-dir "myapp") ; => "/home/user/.config/myapp"
;; Under systemd system unit with ConfigurationDirectory=myapp:
(config-dir) ; => "/etc/myapp"
(config-dir "myapp") ; => "/etc/myapp" (no double append)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L502-L549)
---
## cache-dir
```clojure
(cache-dir)
(cache-dir app-name)
```
Returns the directory for application cache files.
With no arguments, returns the base directory (caller appends app name).
With `app-name`, appends it as a subdirectory (unless systemd provides one).
Checks environment variables in order:
1. `$CACHE_DIRECTORY` - set by systemd for system units (already app-specific)
2. `$XDG_CACHE_HOME` - XDG base directory spec / systemd user units
3. Platform default
When systemd sets `$CACHE_DIRECTORY`, it may contain multiple colon-separated
paths if the unit configures multiple directories; this function returns the
first path.
Platform defaults when no environment variable is set:
| | |
| --- | --- |
| platform | path |
| Linux | `$HOME/.cache` |
| macOS | `$HOME/Library/Caches` |
| Windows | `%LOCALAPPDATA%` |
Returns `nil` if a suitable directory cannot be determined.
```clojure
(cache-dir) ; => "/home/user/.cache"
(cache-dir "myapp") ; => "/home/user/.cache/myapp"
;; Under systemd system unit with CacheDirectory=myapp:
(cache-dir) ; => "/var/cache/myapp"
(cache-dir "myapp") ; => "/var/cache/myapp" (no double append)
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L551-L598)
---
## data-dir
```clojure
(data-dir)
(data-dir app-name)
```
Returns the directory for persistent application data.
This is the recommended directory for ACME certificates and keys because they
are valuable cryptographic material with rate limits on reissuance.
With no arguments, returns the base directory.
With `app-name`, appends it as a subdirectory.
Checks environment variables in order:
1. `$STATE_DIRECTORY` - set by systemd for the host application
2. `$XDG_DATA_HOME` - XDG base directory spec
3. Platform default
When systemd sets `$STATE_DIRECTORY`, it may contain multiple colon-separated
paths if the unit configures multiple directories; this function returns the
first path.
Platform defaults when no environment variable is set:
| | |
| --- | --- |
| platform | path |
| Linux | `$HOME/.local/share` |
| macOS | `$HOME/Library/Application Support` |
| Windows | `%LOCALAPPDATA%` |
Returns `nil` if a suitable directory cannot be determined.
```clojure
(data-dir) ; => "/home/user/.local/share"
(data-dir "myapp") ; => "/home/user/.local/share/myapp"
;; Under systemd system unit with StateDirectory=my-app:
(data-dir) ; => "/var/lib/my-app"
(data-dir "ol.clave") ; => "/var/lib/my-app/ol.clave"
(file-storage {:root (data-dir "ol.clave")})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L600-L649)
---
## file-storage
```clojure
(file-storage)
(file-storage opts)
```
Creates a [`FileStorage`](#filestorage).
With no arguments, storage defaults to the `"ol.clave"` subdirectory
inside [`data-dir`](#data-dir).
With one argument, pass an opts map.
Options:
| key | description
|---------|-------------
| `:root` | Required root directory as a string or `java.nio.file.Path`
The root directory is created if it does not exist.
Returns a record implementing [`ol.clave.storage/Storage`](api/ol-clave-storage.adoc#Storage) and
[`ol.clave.storage/TryLocker`](api/ol-clave-storage.adoc#TryLocker).
Example:
```clojure
(file-storage {:root "/var/lib/myapp"})
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage/file.clj#L651-L689)
## ol.clave.storage
# ol.clave.storage
Key-value storage with path semantics for ACME certificate data.
The storage API provides a unified abstraction for persisting certificates,
account keys, and lock state.
Keys use forward slashes with no leading or trailing slashes.
A key with an associated value is a "file"; a key with no value that serves
as a prefix for other keys is a "directory".
Keys passed to [`load`](#load) and [`store`](#store) always have file semantics; directories
are implicit from the path structure.
## Key Format
Keys follow path semantics similar to filesystem paths.
A "prefix" is defined on a component basis: `"a"` is a prefix of `"a/b"`
but not of `"ab/c"`.
* Valid: `"acme/certs/example.com"`
* Valid: `"locks/renewal.lock"`
* Invalid: `"/leading/slash"` (leading slash removed by normalization)
* Invalid: `"trailing/slash/"` (trailing slash removed by normalization)
Use [`storage-key`](#storage-key) to safely join key components and [`safe-key`](#safe-key) to sanitize
user-provided values for use as key segments.
## Lease Integration
All operations accept a `lease` from [`ol.clave.lease`](api/ol-clave-lease.adoc) for cooperative
cancellation and deadline propagation.
Pass `nil` to skip cancellation checks for short, non-interruptible operations.
Implementations must honor lease cancellation and throw promptly when the
lease is no longer active.
## Locking
The [`lock`](#lock) and [`unlock`](#unlock) methods provide advisory locking to coordinate
expensive operations across processes.
You do not need to wrap every storage call in a lock; [`store`](#store), [`load`](#load),
and other basic operations are already thread-safe.
Use locking for higher-level operations that need synchronization, such as
certificate renewal where only one process should attempt issuance at a time.
When the lock guards an idempotent operation, always verify that the work
still needs to be done after acquiring the lock.
Another process may have completed the task while you were waiting.
Use [`with-lock`](#with-lock) for safe lock acquisition with guaranteed release.
Optional protocols [`TryLocker`](#trylocker) and [`LockLeaseRenewer`](#lockleaserenewer) extend locking
with non-blocking acquisition and lease renewal for long-running operations.
## Thread Safety
Implementations must be safe for concurrent use from multiple threads.
Methods should block until their operation is complete: [`load`](#load) should
always return the value from the last call to [`store`](#store) for a given key,
and concurrent calls to [`store`](#store) must not corrupt data.
Callers will typically invoke storage methods from virtual threads, so
blocking I/O is expected and appropriate.
Implementors do not need to spawn threads or perform asynchronous operations
internally.
This is not a streaming API and is not suitable for very large files.
## Usage
```clojure
(require '[ol.clave.storage :as s]
'[ol.clave.storage.file :as fs])
(let [storage (fs/file-storage {:root "/var/acme"})]
;; Store and retrieve data
(s/store-string storage nil "certs/example.com" cert-pem)
(s/load-string storage nil "certs/example.com")
;; List with prefix
(s/list storage nil "certs" false) ; => ["certs/example.com" ...]
;; Coordinated access for expensive operations
(s/with-lock storage lease "certs/example.com"
(fn []
;; Check if work still needed after acquiring lock
(when (certificate-needs-renewal? storage "example.com")
(renew-certificate!)))))
```
## Related Namespaces
* [`ol.clave.storage.file`](api/ol-clave-storage-file.adoc) - Filesystem storage implementation
* [`ol.clave.lease`](api/ol-clave-lease.adoc) - Cooperative cancellation
This interface was inspired by certmagic’s interface in Go.
## ->KeyInfo
```clojure
(->KeyInfo key modified size terminal?)
```
Metadata about a storage key.
The `key` and `terminal?` fields are required.
The `modified` and `size` fields are optional if the storage implementation
cannot provide them, but setting them makes certain operations more consistent
and predictable.
Fields:
- `key` - the storage key as a string
- `modified` - last modification time as `java.time.Instant`, or `nil`
- `size` - size in bytes (Long), or `nil`
- `terminal?` - `false` for directories (keys that act as prefix for other
keys), `true` for files (keys with associated values)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L104-L119)
---
## KeyInfo
Metadata about a storage key.
The `key` and `terminal?` fields are required.
The `modified` and `size` fields are optional if the storage implementation
cannot provide them, but setting them makes certain operations more consistent
and predictable.
Fields:
- `key` - the storage key as a string
- `modified` - last modification time as `java.time.Instant`, or `nil`
- `size` - size in bytes (Long), or `nil`
- `terminal?` - `false` for directories (keys that act as prefix for other
keys), `true` for files (keys with associated values)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L104-L119)
---
## map->KeyInfo
```clojure
(map->KeyInfo m)
```
Metadata about a storage key.
The `key` and `terminal?` fields are required.
The `modified` and `size` fields are optional if the storage implementation
cannot provide them, but setting them makes certain operations more consistent
and predictable.
Fields:
- `key` - the storage key as a string
- `modified` - last modification time as `java.time.Instant`, or `nil`
- `size` - size in bytes (Long), or `nil`
- `terminal?` - `false` for directories (keys that act as prefix for other
keys), `true` for files (keys with associated values)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L104-L119)
---
## Storage
Key-value storage with path semantics.
All methods accept a `lease` from [`ol.clave.lease`](api/ol-clave-lease.adoc) for cooperative
cancellation; pass `nil` to skip cancellation checks.
Keys are normalized: backslashes become forward slashes, leading and trailing
slashes are stripped.
Implementations must be safe for concurrent use.
Methods should block until their operation is complete.
[`load`](#load), [`delete`](#delete), [`list`](#list), and [`stat`](#stat) should throw
`java.nio.file.NoSuchFileException` when the key does not exist.
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L121-L201)
### store
```clojure
(store this lease key value-bytes)
```
Stores `value-bytes` at `key`, creating parent directories as needed.
Overwrites any existing value at this key.
Concurrent calls to <> must not corrupt data.
Returns `nil`.
---
### load
```clojure
(load this lease key)
```
Returns the bytes stored at `key`.
Always returns the value from the last successful <> for this key.
Throws `java.nio.file.NoSuchFileException` if `key` does not exist.
---
### delete
```clojure
(delete this lease key)
```
Deletes `key` and any keys prefixed by it (recursive delete).
If `key` is a directory (prefix of other keys), all keys with that prefix
are deleted.
Returns `nil`.
Throws `java.nio.file.NoSuchFileException` if `key` does not exist.
---
### exists?
```clojure
(exists? this lease key)
```
Returns `true` if `key` exists as a file or directory, `false` otherwise.
---
### list
```clojure
(list this lease prefix recursive?)
```
Lists keys under `prefix`.
When `recursive?` is `false`, returns only keys prefixed exactly by
`prefix` (direct children).
When `recursive?` is `true`, non-terminal keys are enumerated and all
descendants are returned.
Returns a vector of key strings.
Throws `java.nio.file.NoSuchFileException` if `prefix` does not exist.
---
### stat
```clojure
(stat this lease key)
```
Returns a [`KeyInfo`](#keyinfo) record describing `key`.
Throws `java.nio.file.NoSuchFileException` if `key` does not exist.
---
### lock
```clojure
(lock this lease name)
```
Acquires an advisory lock for `name`, blocking until available.
Only one lock for a given name can exist at a time.
A call to <> for a name that is already locked blocks until the
lock is released or becomes stale.
Lock names are sanitized via <>.
Implementations must honor lease cancellation.
Returns `nil` when the lock is acquired.
---
### unlock
```clojure
(unlock this lease name)
```
Releases the advisory lock for `name`.
This method must only be called after a successful call to <>, and
only after the critical section is finished, even if it threw an exception.
<> cleans up any resources allocated during <>.
Returns `nil`.
Throws only if the lock could not be released.
---
## TryLocker
Optional non-blocking lock acquisition.
Implementations that support non-blocking lock attempts should extend this
protocol in addition to [`Storage`](#storage).
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L203-L218)
### try-lock
```clojure
(try-lock this lease name)
```
Attempts to acquire the lock for `name` without blocking.
Returns `true` if the lock was acquired, `false` if it could not be
obtained (e.g., already held by another process).
Implementations must honor lease cancellation.
After a successful <>, you must call <> when the
critical section is finished, even if it threw an exception.
---
## LockLeaseRenewer
Optional lease renewal for long-running locks.
When a lock is held for an extended period, the holder should periodically
renew the lock lease to prevent it from being considered stale and forcibly
released by another process.
This is useful for long-running operations that need synchronization.
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L220-L237)
### renew-lock-lease
```clojure
(renew-lock-lease this lease lock-key lease-duration)
```
Extends the lease on `lock-key` by `lease-duration`.
This prevents another process from acquiring the lock by treating it as
stale.
`lease-duration` is a `java.time.Duration`.
Returns `nil`.
Throws if the lock is not currently held or could not be renewed.
---
## safe-key
```clojure
(safe-key s)
```
Returns a filesystem-safe key component from `s`.
Transforms:
- Converts to lowercase
- Replaces spaces with underscores
- Replaces `+` with `_plus_`, `*` with `wildcard_`
- Replaces `:` with `-`
- Removes `..` sequences
- Strips characters not in `[a-zA-Z0-9_@.-]`
Use this when incorporating user input or domain names into storage keys.
```clojure
(safe-key "Example.COM") ; => "example.com"
(safe-key "*.example.com") ; => "wildcard_example.com"
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L241-L267)
---
## storage-key
```clojure
(storage-key & parts)
```
Joins key components with `/`, ignoring `nil` and blank parts.
```clojure
(storage-key "acme" "certs" "example.com")
;; => "acme/certs/example.com"
(storage-key "base" nil "" "file")
;; => "base/file"
```
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L269-L284)
---
## store-string
```clojure
(store-string storage lease key s)
```
Stores UTF-8 encoded `s` at `key`.
Convenience wrapper around [`store`](#store) for text content.
See [`load-string`](#load-string) for retrieval.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L286-L292)
---
## load-string
```clojure
(load-string storage lease key)
```
Loads UTF-8 text from `key`.
Convenience wrapper around [`load`](#load) for text content.
Throws `java.nio.file.NoSuchFileException` if `key` does not exist.
See [`store-string`](#store-string) for storage.
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L294-L301)
---
## with-lock
```clojure
(with-lock storage lease lock-name f)
```
Executes `f` while holding the lock `lock-name`, releasing on exit.
Acquires the lock via [`lock`](#lock), runs `f` (a zero-argument function), and
releases via [`unlock`](#unlock) in a finally block regardless of success or failure.
If the lock guards an idempotent operation, `f` should verify that the work
still needs to be done.
Another process may have completed the task while you were waiting to acquire
the lock.
```clojure
(with-lock storage lease "certs/example.com"
(fn []
;; Check if work still needed after acquiring lock
(when (certificate-needs-renewal? domain)
(renew-certificate!)
(write-certificate!))))
```
See also: [`lock`](#lock), [`unlock`](#unlock)
[source,window=_blank](https://github.com/outskirtslabs/clave/blob/main/src/ol/clave/storage.clj#L303-L329)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## UNRELEASED
## `v0.0.1` (2026-XX-XX)
We haven’t quite got here yet..
Please report any problems and let me know if anything is unclear or inconvenient. Thank you.
## ol.clave
# ol.clave
> Automatic HTTPS certificate management and renewal via ACME, implemented in pure Clojure with minimal dependencies



`ol.clave` is an [ACME client](https://datatracker.ietf.org/doc/html/rfc8555) for Clojure.
It gives you a lower-level API for implementing the ACME protocol itself when you need that much control. If you want to work directly with accounts, orders, authorizations, challenges, and certificate issuance, you can do that.
And if you do not want to live at RFC altitude all day, it also gives you a higher-level API for provisioning certificates from application code, managing them over time, and keeping them up to date. That is the part most people actually want. Ask for a cert. Renew the cert. Repeat.
`ol.clave` also includes adapters for wiring ACME into Jetty-based applications, plus helpers for HTTP-01 and TLS-ALPN-01 validation. For DNS validation, it gives you the hooks you need, but it does not ship DNS provider integrations by default. DNS provider integrations are future work. (If you are interested in that, [get in touch](https://casey.link/about).)
One of the design goals for `ol.clave` was as few dependencies as possible and avoid dragging in a bunch of Java ecosystem baggage.
At runtime there are exactly two hard dependencies. The first is [`babashka/json`](https://github.com/babashka/json), which is a BYO JSON-library-library. On the JVM it will use a JSON provider from your classpath. In many applications that will just work because you already have one. If you care which provider gets picked, or want to force one, go read that project’s docs.
The second runtime dep is [Peter Taoussanis’s Trove](https://github.com/taoensso/trove), which gives `ol.clave` a very lightweight way to emit signals and logging without forcing a backend on you. Same story there: wire it into whatever telemetry or logging stack you already use after reading Peter’s docs.
The no-dependencies choice has consequences. I did not want to pull in Bouncy Castle as a runtime dependency. It is large, it is heavy, and it is the kind of library that likes to start version fights when two things on your classpath want different releases. But ACME certificate provisioning still needs some plumbing that the JDK does not hand you directly (despite being distributed as part of the keytool util in every JDK package), especially around DER de/encoding, CSR generation, and a few X.509-adjacent details.
To be clear, `ol.clave` is not implementing any cryptography itself. The actual crypto primitives (RSA, ECC, etc) still come from the JVM. What `ol.clave` implements in pure Clojure is the narrow slice of encoding, decoding, and certificate-request machinery needed to make ACME work without the heavyweight runtime dependencies. That problem space is small enough to specify, small enough to test thoroughly and it isn’t a general-purpose PKI toolkit.
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Installation
Use `deps.edn` git coordinates:
```clojure
;; deps.edn
{:deps {com.outskirtslabs/clave
{:git/url "https://github.com/outskirtslabs/clave.git"
:git/sha "8ae16bc3d6431e6a03d96a6a041e64c24e76376a"}}}
```
## Quick Start
Start with one of the runnable examples in this repository:
* [`examples/certificate.clj`](https://github.com/outskirtslabs/clave/blob/main/examples/certificate.clj) for the higher-level certificate acquisition flow.
* [`examples/acme.clj`](https://github.com/outskirtslabs/clave/blob/main/examples/acme.clj) for a lower-level step-by-step ACME transaction.
* [`examples/ring_jetty.clj`](https://github.com/outskirtslabs/clave/blob/main/examples/ring_jetty.clj) for auto-renewing HTTPS with `ring-jetty-adapter`.
These examples use [Pebble](https://github.com/letsencrypt/pebble) for local ACME testing.
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.clave/next/)
* [API Reference](https://docs.outskirtslabs.com/ol.clave/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/clave/issues)
## Recommended Reading
* [RFC 8555: Automatic Certificate Management Environment (ACME)](https://datatracker.ietf.org/doc/html/rfc8555)
* [RFC 9773: ACME Renewal Information (ARI) Extension](https://datatracker.ietf.org/doc/html/rfc9773)
* [Best Practices for ACME Client Operations](https://github.com/https-dev/docs/blob/master/acme-ops.md)
## Security
See [Security](security.adoc) for security reporting and policy links.
## License
Copyright (C) 2025-2026 Casey Link
Distributed under the [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
Some files included in this project are from third-party sources and retain their original licenses as indicated in [NOTICE](./NOTICE).
Special thanks to [Michiel Borkent (@borkdude)](https://github.com/borkdude/) for the use of [babashka/http-client](https://github.com/babashka/http-client) and [babashka/json](https://github.com/babashka/json).
## Security
# Security
Please report vulnerabilities through [GitHub Security Advisories](https://github.com/outskirtslabs/clave/security/advisories).
For general policy and support expectations, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
## ol.client-ip.cidr
# ol.client-ip.cidr
## cidr-parts
```clojure
(cidr-parts cidr)
```
Parse an ip network string into its prefix and signifcant bits
`cidr` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1)
Returns a vector of [^InetAddress prefix ^Integer bits]
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L26-L39)
---
## contains?
```clojure
(contains? cidr ip)
(contains? cidr-ip cidr-mask ip)
```
Check if the given CIDR contains the given IP address.
Arguments:
cidr - CIDR notation string (e.g. "192.168.0.0/24" or "2001:db8::/32")
ip - IP address to check (can be InetAddress or string)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L41-L61)
---
## reserved-ranges
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L66-L93)
---
## reserved?
```clojure
(reserved? ip)
```
Check if an IP is in a reserved range (loopback or private network).
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/cidr.clj#L95-L98)
## ol.client-ip.core
# ol.client-ip.core
Core implementation of the client-ip library for extracting client IPs from forwarding headers.
The client-ip library is designed to accurately determine the original client IP address
from HTTP forwarding headers in Ring applications. It uses a strategy-based approach
where different strategies handle different network configurations.
## Main Features
* Strategy-based IP detection for different network setups
* Processes IPs according to the chosen strategy to prevent spoofing
* Supports all common headers: X-Forwarded-For, X-Real-IP, Forwarded (RFC 7239), etc.
## Usage
The main entry point is the `wrap-client-ip` middleware function that requires
a strategy and adds the `:ol/client-ip` key to the Ring request map:
```clojure
(require '[ol.client-ip.core :refer [wrap-client-ip]]
'[ol.client-ip.strategy :as strategy])
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/rightmost-non-private-strategy "x-forwarded-for")})
;; other middleware
))
```
You can also use `from-request` to extract client IPs directly from requests:
```clojure
(from-request request (strategy/rightmost-trusted-count-strategy "x-forwarded-for" 2))
;; => "203.0.113.195"
```
See the strategy documentation for more details on choosing the right strategy.
## from-request
```clojure
(from-request request strategy)
```
Extract the client IP from a Ring request using the specified strategy.
This is the core function for determining client IPs. It takes a Ring request
and a strategy instance, then uses the strategy to determine the client IP
from the request headers and remote address.
Refer to ns docs and the [`wrap-client-ip`](#wrap-client-ip) docstring for more usage information.
Returns the client InetAddress , or nil if no client IP can be determined.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/core.clj#L43-L58)
---
## wrap-client-ip
```clojure
(wrap-client-ip handler options)
```
Ring middleware that adds the client IP to the request map using strategies.
This middleware extracts the original client IP address using the specified
strategy and adds it to the request map as `:ol/client-ip`. The strategy determines
how headers are processed and which IP is considered the client IP.
## Options
The options map must contain:
* `:strategy` - A strategy instance (required)
## Strategy Selection
Choose the strategy that matches your network configuration:
* `RemoteAddrStrategy` - Direct connections (no reverse proxy)
* `SingleIPHeaderStrategy` - Single trusted reverse proxy with single IP headers
* `RightmostNonPrivateStrategy` - Multiple proxies, all with private IPs
* `RightmostTrustedCountStrategy` - Fixed number of trusted proxies
* `RightmostTrustedRangeStrategy` - Known trusted proxy IP ranges
* `ChainStrategy` - Try multiple strategies with fallback
## Examples
```clojure
;; Single reverse proxy with X-Real-IP
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/single-ip-header-strategy "x-real-ip")})
;; other middleware
))
;; Multiple proxies with private IPs
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/rightmost-non-private-strategy "x-forwarded-for")})
;; other middleware
))
;; Chain strategy with fallback
(def app
(-> handler
(wrap-client-ip {:strategy
(strategy/chain-strategy
[(strategy/single-ip-header-strategy "x-real-ip")
(strategy/remote-addr-strategy)])})
;; other middleware
))
```
The middleware adds the `:ol/client-ip` key to the request map, containing the determined
client InetAddress or nil.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/core.clj#L60-L121)
## ol.client-ip.ip
# ol.client-ip.ip
IP address utilities for conversion and classification.
This namespace provides functions for working with IP addresses,
particularly for converting between string, InetAddress, and numeric
representations. It follows RFC 5952 for IPv6 address formatting and
provides utilities for type checking and serialization.
**📌 NOTE**\
None of the functions in this namespace accept ip addreses input as strings.
Use [`ol.client-ip.parse-ip/from-string`](api/ol-client-ip-parse-ip.adoc#from-string) to convert strings to InetAddress objects.
The key operations available are:
* Type checking with `ipv4?` and `ipv6?`
* String formatting with `format-ip`
* Numeric conversion with `->numeric` and `numeric->`
These functions are designed to work with the `java.net.InetAddress`
class hierarchy and its subclasses `Inet4Address` and `Inet6Address`.
Refer to [`ol.client-ip.parse-ip`](api/ol-client-ip-parse-ip.adoc) and [`ol.client-ip.cidr`](api/ol-client-ip-cidr.adoc) for more useful functions.
## ipv4?
```clojure
(ipv4? ip)
```
Checks if an IP address is an IPv4 address.
This function determines if the given IP address is an instance of
`java.net.Inet4Address`.
Arguments:
ip - An IP address object (InetAddress)
Returns:
`true` if the IP address is IPv4, `false` otherwise
Examples:
```clojure
(ipv4? (parse-ip/from-string "192.168.1.1")) ;; => true
(ipv4? (parse-ip/from-string "::1")) ;; => false
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L28-L46)
---
## ipv6?
```clojure
(ipv6? ip)
```
Checks if an IP address is an IPv6 address.
This function determines if the given IP address is an instance of
`java.net.Inet6Address`.
Arguments:
ip - An IP address object (InetAddress)
Returns:
`true` if the IP address is IPv6, `false` otherwise
Examples:
```clojure
(ipv6? (parse-ip/from-string "2001:db8::1")) ;; => true
(ipv6? (parse-ip/from-string "192.168.1.1")) ;; => false
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L48-L66)
---
## format-ipv6
```clojure
(format-ipv6 ip)
```
Converts an IPv6 address to its canonical string representation.
This function formats IPv6 addresses according to RFC 5952, which includes:
- Using lowercase hexadecimal digits
- Compressing the longest run of consecutive zero fields with ::
- Not using :: to compress a single zero field
- Including scope IDs with % delimiter for link-local addresses
Arguments:
ip - An IPv6 address object (Inet6Address)
Returns:
A string representation of the IPv6 address formatted according to RFC 5952
Examples:
```clojure
(format-ipv6 (parse-ip/from-string "2001:db8:0:0:0:0:0:1"))
;; => "2001:db8::1"
(format-ipv6 (parse-ip/from-string "fe80::1%eth0"))
;; => "fe80::1%eth0"
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L121-L152)
---
## format-ip
```clojure
(format-ip ip)
```
Converts an IP address to its canonical string representation.
This function formats IP addresses according to standard conventions:
- IPv4 addresses use dotted decimal notation (e.g., "192.168.0.1")
- IPv6 addresses follow RFC 5952 with compressed notation using ::
for the longest run of zeros, and embedded IPv4 addresses where appropriate
The implementation handles scope IDs for IPv6 link-local addresses
and properly compresses IPv6 addresses according to the standard rules.
Arguments:
ip - An IP address object (InetAddress)
Returns:
A string representation of the IP address
Throws:
IllegalArgumentException - if the input is not a valid InetAddress
Examples:
```clojure
(format-ip (parse-ip/from-string "192.168.1.1")) ;; => "192.168.1.1"
(format-ip (parse-ip/from-string "2001:db8::1")) ;; => "2001:db8::1"
(format-ip (parse-ip/from-string "::ffff:1.2.3.4")) ;; => "::ffff:1.2.3.4"
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L154-L185)
---
## ->numeric
```clojure
(->numeric ip)
```
Converts an IP address to its numeric representation as a BigInteger.
This function takes an InetAddress (either IPv4 or IPv6) and returns
a BigInteger representing the address. The BigInteger can be used for
IP address arithmetic, comparison, or storage.
Arguments:
ip - An IP address object (InetAddress)
Returns:
A BigInteger representing the IP address
Examples:
```clojure
(->numeric (parse-ip/from-string "192.0.2.1"))
;; => 3221225985
(->numeric (parse-ip/from-string "2001:db8::1"))
;; => 42540766411282592856903984951653826561
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L187-L209)
---
## numeric->
```clojure
(numeric-> address version)
```
Converts a BigInteger to an InetAddress.
This function takes a numeric representation of an IP address as a BigInteger
and converts it back to an InetAddress object. The `ipv6?` flag determines
whether to create an IPv4 or IPv6 address.
Arguments:
address - A BigInteger representing the IP address
version - One of :v6 or :v4 indicating whether this is an IPv6 address
or an IPv4 address
Returns:
An InetAddress object representing the numeric address, or nil
Throws:
ExceptionInfo - If the BigInteger is negative or too large for the
specified address type
Examples:
```clojure
;; Convert back to IPv4
(numeric-> (BigInteger. "3221226113") :v4)
;; => #object[java.net.Inet4Address 0x578d1c5 "/192.0.2.129"]
;; Convert back to IPv6
(numeric-> (BigInteger. "42540766411282592856903984951653826561") :v6)
;; => #object[java.net.Inet6Address 0x14832c23 "/2001:db8:0:0:0:0:0:1"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L211-L267)
---
## numeric-v6->
```clojure
(numeric-v6-> address)
```
Converts a BigInteger to an IPv6 address.
This is a convenience wrapper around `numeric->` that automatically
sets the `ipv6?` flag to true.
Arguments:
address - A BigInteger representing the IPv6 address
Returns:
An Inet6Address object or nil if the input is invalid
See also: <<numeric--GT-,`numeric->`>>
Examples:
```clojure
(numeric-v6-> (BigInteger. "42540766411282592856903984951653826561"))
;; => #object[java.net.Inet6Address 0x42668812 "/2001:db8:0:0:0:0:0:1"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L269-L292)
---
## numeric-v4->
```clojure
(numeric-v4-> address)
```
Converts a BigInteger to an IPv4 address.
Arguments:
address - A BigInteger representing the IPv4 address
Returns:
An Inet4Address object or nil if the input is invalid
See also: <<numeric--GT-,`numeric->`>>
Examples:
```clojure
(numeric-v4-> (BigInteger. "3221226113"))
;; => #object[java.net.Inet4Address 0x6b88aeff "/192.0.2.129"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/ip.clj#L294-L314)
## ol.client-ip.parse-ip
# ol.client-ip.parse-ip
Parse a string representation of an IPv4 or IPv6 address into an InetAddress instance.
Unlike `InetAddress/getByName`, the functions in this namespace never cause
DNS services to be accessed. This avoids potentially blocking/side-effecting
network calls that can occur when using the JDK’s built-in methods to parse IP
addresses.
This implementation is inspired by Google Guava’s InetAddresses class, which
provides similar functionality in Java. This code focuses on strict validation
of IP address formats according to relevant RFCs.
Features:
- Non-blocking IP address parsing with pure functions
- Strict RFC-compliant validation
- Support for all IPv4 formats
- Support for all IPv6 formats (including compressed notation and embedded IPv4)
- Support for IPv6 scope identifiers
The main entry point is `parse-ip`, which takes a string and returns an
InetAddress instance or nil if the input is an invalid ip address literal.
## bytes->inet-address
```clojure
(bytes->inet-address addr)
(bytes->inet-address addr scope)
```
Convert a byte array into an InetAddress.
Args:
addr: the raw 4-byte or 16-byte IP address in big-endian order
scope: optional scope identifier for IPv6 addresses
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/parse_ip.clj#L231-L256)
---
## from-string
```clojure
(from-string ip-string)
```
Returns the InetAddress having the given string representation or nil otherwise.
This function parses IP address strings without performing DNS lookups, making it
suitable for environments where DNS lookups would cause unwanted blocking or side effects.
It supports:
- IPv4 addresses in dotted decimal format (e.g., "192.168.1.1")
- IPv6 addresses in hex format with optional compression (e.g., "2001:db8::1")
- IPv6 addresses with scope IDs (e.g., "fe80::1%eth0" or "fe80::1%1")
- IPv6 addresses with embedded IPv4 (e.g., "::ffff:192.168.1.1")
If the input is already an InetAddress, it is returned unchanged.
Args:
ip-string: A string representing an IP address or an InetAddress
Returns:
An InetAddress object, or nil if the input couldn’t be parsed
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/parse_ip.clj#L258-L290)
## ol.client-ip.protocols
# ol.client-ip.protocols
## ClientIPStrategy
Protocol for determining client IP from headers and remote address.
Returns InetAddress or nil if no valid client ip is found.
Implementations should:
* Be thread-safe and reusable across requests
* Validate configuration at creation time (throw on invalid config)
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/protocols.clj#L5-L21)
### client-ip
```clojure
(client-ip this headers remote-addr)
```
Extract the client IP from request headers and remote address.
Args:
headers: Ring-style headers map (lowercase keys)
remote-addr: String from Ring request :remote-addr
Returns:
InetAddress or nil if no valid client IP found.
## ol.client-ip.strategy
# ol.client-ip.strategy
Strategy implementations for determining client IP addresses.
This namespace provides various strategy implementations for extracting the
real client IP from HTTP headers and connection information.
Each strategy is designed for specific network configurations:
* `RemoteAddrStrategy` - Direct connections (no reverse proxy)
* `SingleIPHeaderStrategy` - Single trusted reverse proxy with single IP headers
* `RightmostNonPrivateStrategy` - Multiple proxies, all with private IPs
* `LeftmostNonPrivateStrategy` - Get IP closest to original client (not secure)
* `RightmostTrustedCountStrategy` - Fixed number of trusted proxies
* `RightmostTrustedRangeStrategy` - Known trusted proxy IP ranges
* `ChainStrategy` - Try multiple strategies in order
Strategies are created once and reused across requests. They are thread-safe
and designed to fail fast on configuration errors while being resilient to
malformed input during request processing.
## parse-forwarded
```clojure
(parse-forwarded header-value)
```
Parse a Forwarded header value according to RFC 7239.
Returns a sequence of InetAddress objects, or an empty sequence if none are valid.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L119-L130)
---
## split-host-zone
```clojure
(split-host-zone ip-string)
```
Split IPv6 zone from IP address.
IPv6 addresses can include zone identifiers (scope IDs) separated by '%'.
For example: 'fe80::1%eth0' or 'fe80::1%1'
Args:
ip-string: String IP address that may contain a zone identifier
Returns:
Vector of [ip zone] where zone is the zone identifier string,
or [ip nil] if no zone is present.
Examples:
(split-host-zone "192.168.1.1") => ["192.168.1.1" nil]
(split-host-zone "fe80::1%eth0") => ["fe80::1" "eth0"]
(split-host-zone "fe80::1%1") => ["fe80::1" "1"]
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L150-L171)
---
## remote-addr-strategy
```clojure
(remote-addr-strategy)
```
Create a strategy that uses the direct client socket IP.
This strategy extracts the IP address from the request’s :remote-addr field,
which represents the direct client socket connection. Use this when your
server accepts direct connections from clients (not behind a reverse proxy).
The strategy strips any port information and validates the IP is not
zero/unspecified (0.0.0.0 or ::).
Returns:
RemoteAddrStrategy instance
Example:
(remote-addr-strategy)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L184-L200)
---
## single-ip-header-strategy
```clojure
(single-ip-header-strategy header-name)
```
Create a strategy that extracts IP from headers containing a single IP address.
This strategy is designed for headers like X-Real-IP, CF-Connecting-IP,
True-Client-IP, etc. that contain only one IP address. Use this when you
have a single trusted reverse proxy that sets a reliable single-IP header.
The strategy validates that the header name is not X-Forwarded-For or
Forwarded, as these headers can contain multiple IPs and should use
different strategies.
Args:
header-name: String name of the header to check (case-insensitive)
Returns:
SingleIPHeaderStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to multi-IP headers
Examples:
(single-ip-header-strategy "x-real-ip")
(single-ip-header-strategy "cf-connecting-ip")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L208-L235)
---
## rightmost-non-private-strategy
```clojure
(rightmost-non-private-strategy header-name)
```
Create a strategy that gets the rightmost non-private IP from forwarding headers.
This strategy processes IPs in reverse order (rightmost first) and returns
the first IP that is not in a private/reserved range. Use this when all
your reverse proxies have private IP addresses, so the rightmost non-private
IP should be the real client IP.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
Returns:
RightmostNonPrivateStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to single-IP headers
Examples:
(rightmost-non-private-strategy "x-forwarded-for")
(rightmost-non-private-strategy "forwarded")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L250-L278)
---
## leftmost-non-private-strategy
```clojure
(leftmost-non-private-strategy header-name)
```
Create a strategy that gets the leftmost non-private IP from forwarding headers.
This strategy processes IPs in forward order (leftmost first) and returns
the first IP that is not in a private/reserved range. This gets the IP
closest to the original client.
⚠️ ***WARNING: NOT FOR SECURITY USE*** ⚠️
This strategy is easily spoofable since clients can add arbitrary IPs to
the beginning of forwarding headers. Use only when security is not a concern
and you want the IP closest to the original client.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
Returns:
LeftmostNonPrivateStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to single-IP headers
Examples:
(leftmost-non-private-strategy "x-forwarded-for")
(leftmost-non-private-strategy "forwarded")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L293-L325)
---
## rightmost-trusted-count-strategy
```clojure
(rightmost-trusted-count-strategy header-name trusted-count)
```
Create a strategy that returns IP at specific position based on known proxy count.
This strategy is for when you have a fixed number of trusted proxies and want
to get the IP at the specific position that represents the original client.
Given N trusted proxies, the client IP should be at position -(N+1) from the end.
For example, with 2 trusted proxies and header 'client, proxy1, proxy2, proxy3':
- Total IPs: 4
- Skip last 2 (trusted): positions 2,3
- Return IP at position 1 (proxy1)
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
trusted-count: Number of trusted proxies (must be > 0)
Returns:
RightmostTrustedCountStrategy instance
Throws:
ExceptionInfo if header-name is invalid or trusted-count <= 0
Examples:
(rightmost-trusted-count-strategy "x-forwarded-for" 1)
(rightmost-trusted-count-strategy "forwarded" 2)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L338-L371)
---
## rightmost-trusted-range-strategy
```clojure
(rightmost-trusted-range-strategy header-name trusted-ranges)
```
Create a strategy that returns rightmost IP not in trusted ranges.
This strategy processes IPs in reverse order (rightmost first) and returns
the first IP that is not within any of the specified trusted IP ranges.
Use this when you know the specific IP ranges of your trusted proxies.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
trusted-ranges: Collection of CIDR ranges (strings) that represent trusted proxies
Each range should be a valid CIDR notation like "192.168.1.0/24"
Returns:
RightmostTrustedRangeStrategy instance
Throws:
ExceptionInfo if header-name is invalid or trusted-ranges is empty/invalid
Examples:
(rightmost-trusted-range-strategy "x-forwarded-for" ["10.0.0.0/8" "192.168.0.0/16"])
(rightmost-trusted-range-strategy "forwarded" ["172.16.0.0/12"])
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L386-L424)
---
## chain-strategy
```clojure
(chain-strategy strategies)
```
Create a strategy that tries multiple strategies in order until one succeeds.
This strategy allows fallback scenarios where you want to try different
IP detection methods in priority order. Each strategy is tried in sequence
until one returns a non-empty result.
Common use case: Try a single-IP header first, then fall back to remote address.
Args:
strategies: Vector of strategy instances to try in order
Must contain at least one strategy
Returns:
ChainStrategy instance
Throws:
ExceptionInfo if strategies is empty or contains invalid strategies
Examples:
(chain-strategy [(single-ip-header-strategy "x-real-ip")
(remote-addr-strategy)])
(chain-strategy [(rightmost-non-private-strategy "x-forwarded-for")
(single-ip-header-strategy "x-real-ip")
(remote-addr-strategy)])
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/v0.1.x/src/ol/client_ip/strategy.clj#L435-L470)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
## `v0.1.1` (2025-08-18)
Sorry, this is my first from-scratch clojars release, and I’m struggling
to get the metadata working.
There are no changes.
## `v0.1.0` (2025-08-18)
This is the first public release of this codebase under the
`ol.client-ip` moniker.
Variations of this code has been used in production for a long time, but
nonetheless I want to be cautious so we’re starting with sub-1.0
versioning.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
## ol.client-ip
# ol.client-ip
> A 0-dependency ring middleware for determining a request’s real client IP address from HTTP headers




`X-Forwarded-For` and other client IP headers are [often used incorrectly](https://adam-p.ca/blog/2022/03/x-forwarded-for/), resulting in bugs and security vulnerabilities. This library provides strategies for extracting the correct client IP based on your network configuration.
It is based on the golang reference implementation [realclientip/realclientip-go](https://github.com/realclientip/realclientip-go).
Quick feature list:
* ring middleware determining the client’s IP address
* 0 dependency IP address string parsing with guaranteed no trips to the hosts' DNS services, which can block and timeout (unlike Java’s `InetAddress/getByName`)
* rightmost-ish strategies support the `X-Forwarded-For` and `Forwarded` (RFC 7239) headers
* IPv6 zone identifiers support
Note that there is no dependency on ring, the public API could also be used for pedestal or sieppari-style interceptors.
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/client-ip {:mvn/version "0.1.1"}}}
;; Leiningen
[com.outskirtslabs/client-ip "0.1.1"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.client-ip.core :as client-ip]
[ol.client-ip.strategy :as strategy]))
;; Simple case: behind a trusted proxy that sets X-Real-IP
(def app
(-> handler
(client-ip/wrap-client-ip
{:strategy (strategy/single-ip-header-strategy "x-real-ip")})))
;; The client IP is now available in the request
(defn handler [request]
(let [client-ip (:ol/client-ip request)]
{:status 200
:body (str "Your IP is: " client-ip)}))
```
For detailed guidance on choosing strategies, see [Usage Guide](usage.adoc).
Choosing the wrong strategy can result in IP address spoofing security vulnerabilities.
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.client-ip/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.client-ip/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/client-ip/issues)
## Recommended Reading
You think it is an easy question:
> I have an HTTP application, I just want to know the IP address of my client.
But who is the client?
> The computer on the other end of the network connection?
But which network connection? The one connected to your HTTP application is probably a reverse proxy or load balancer.
> Well I mean the "user’s IP address"
It ain’t so easy kid.
There are many good articles on the internet that discuss the perils and pitfalls of trying to answer this deceptively simple question.
You _should_ read one or two of them to get an idea of the complexity in this space. Libraries, like this one, _cannot_ hide the complexity from you, there is no abstraction nor encapsulation nor "default best practice".
Below are some of those good articles:
* MDN: [X-Forwarded-For: Security and privacy concerns](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#security_and_privacy_concerns)
* [The perils of the "real" client IP](https://adam-p.ca/blog/2022/03/x-forwarded-for/) ([archive link](https://web.archive.org/web/20250416042714/https://adam-p.ca/blog/2022/03/x-forwarded-for/))
* [Rails IP Spoofing Vulnerabilities and Protection](https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/) ([archive link](https://web.archive.org/web/20250421121810/https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/))
* [ol.client-ip: A Clojure Library to Prevent IP Spoofing](https://casey.link/blog/client-ip-ring-middleware/)
## Security
See [here](https://github.com/outskirtslabs/client-ip/security/advisories) for security advisories or to report a security vulnerability.
## License
Copyright (C) 2025 Casey Link <casey@outskirtslabs.com>
Distributed under the [MIT License](https://github.com/outskirtslabs/client-ip/blob/main/LICENSE).
## Security policy
# Security policy
## Advisories
All security advisories will be posted
https://github.com/outskirtslabs/client-ip/security/advisories[on
GitHub].
## Reporting a vulnerability
Please report possible security vulnerabilities
https://github.com/outskirtslabs/client-ip/security/advisories[via
GitHub], or by emailing me at `casey@outskirtslabs.com`. You may encrypt
emails with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
— [Casey Link](https://casey.link)
## Usage Guide
# Usage Guide
This guide explains when and why to use each strategy. The choice of strategy is
critical -- _using the wrong strategy can lead to IP spoofing vulnerabilities_.
## Understanding Your Network Setup
Before choosing a strategy, you need to understand your production environment’s network topology:
1. Are you behind reverse proxies? (nginx, Apache, load balancers, CDNs)
2. How many trusted proxies are there?
3. What headers do they set?
The answers determine which strategy is appropriate and secure for your setup.
## Terminology
* **socket-level IP**\
The IP address that a server or proxy observes at the TCP socket level from whatever is directly connecting to it. This is always trustworthy since it cannot be spoofed at the socket level, regardless of whether the connection is from an end user or another proxy in the chain.
* **private IP addresses**\
IP addresses reserved for private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) that are not routable on the public internet. These typically represent your internal infrastructure components.
* **reverse proxy / proxy / load balancer / CDN**\
An intermediary server that sits between clients and your application server, forwarding client requests. Examples include: nginx, Apache, caddy, AWS ALB, Cloudflare, Cloudfront, Fastly, Akamai, Fly.io, Hetzner Load Balancer.
* **trusted proxy**\
A reverse proxy under your control or operated by a service you trust, whose IP forwarding headers you can rely on. The key characteristic is that you know what headers it sets and clients cannot bypass it.
* **IP spoofing**\
The practice of sending packets with a forged source IP address, or in the context of HTTP headers, setting fake client IP values in headers like `X-Forwarded-For` to deceive the application about the request’s true origin.
* **trivially spoofable**\
Headers or IP values that can be easily faked by any client without special network access. For example, any client can send `X-Forwarded-For: 1.2.3.4` in their request.
* **XFF**\
Short for `X-Forwarded-For`, the most common HTTP header used by proxies to communicate the original client IP address through a chain of proxies. Each proxy typically appends the IP it received the request from.
* **rightmost-ish**\
A parsing strategy that extracts IP addresses from the right side of forwarding headers like `X-Forwarded-For`, typically skipping a known number of trusted proxies to find the IP added by the first trusted proxy. This is the only trustworthy approach since you control what your trusted proxies add to the header chain ([see this article](https://adam-p.ca/blog/2022/03/x-forwarded-for)).
* **leftmost-ish**\
A parsing strategy that takes the leftmost (first) IP address in forwarding headers, representing the alleged original client. This is highly untrustworthy and vulnerable to spoofing since any client can set arbitrary values at the beginning of these headers ([see this article](https://adam-p.ca/blog/2022/03/x-forwarded-for)).
## Strategy Reference
### Remote Address Strategy / No middleware at all!
* **When to use**\
Your server accepts direct connections from clients without any reverse proxies or load balancers.
* **Why**\
The socket-level remote address is the actual client IP when there are no intermediaries.
Using this library you can use the remote-addr-strategy, which is provided to be comprehensive, however you can also just use the existing [`:remote-addr`](https://github.com/ring-clojure/ring/blob/master/SPEC.md#remote-addr) key in your ring request map.
```clojure
(strategy/remote-addr-strategy)
;; OR, just fetch the remote addr from the ring request map
(:remote-addr request)
```
* **Example network**
```
Client -> Your Server
```
* **Security**\
Safe -- the remote address cannot be spoofed at the socket level.
---
### Single IP Header Strategy
* **When to use**\
You have a trusted reverse proxy that accepts connections directly from clients and sets a specific header with the client IP.
* **Why**\
Many Cloud Provider’s CDNs and load balancers products provide a single authoritative header with the real client IP. This is the most secure approach when you have such a setup.
* **Provider headers with trusted values**
* Cloudflare (everyone): `cf-connecting-ip` -- this is a socket-level IP value ([docs](https://developers.cloudflare.com/fundamentals/get-started/http-request-headers/))
* Cloudflare (enterprise): `true-client-ip` -- also a socket-level IP value, and just for Enterprise customers with backwards compatibility requirements ([docs](https://developers.cloudflare.com/fundamentals/get-started/http-request-headers/))
* Fly.io: `fly-client-ip` -- the socket ip address ([docs](https://www.fly.io/docs/networking/request-headers/#fly-client-ip))
* Azure FrontDoor: `x-azure-socketip` -- the socket IP address associated with the TCP connection that the current request originated from ([docs](https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol))
* nginx with correctly configured [`ngx_http_realip_module`](https://nginx.org/en/docs/http/ngx_http_realip_module.html)
* Do not use `proxy_set_header X-Real-IP $remote_addr;`
* **Provider headers that require extra config**
These providers offer headers that out of the box are trivially spoofable, and require extra configuration (in your provider’s management interface) to configure securely.
* Akamai: `true-client-ip` -- trivially spoofable by default, refer to [this writeup](https://adam-p.ca/blog/2022/03/x-forwarded-for/#akamai)
* Fastly: `fastly-client-ip` -- trivially spoofable, you must use vcl to configure it [fastly docs](https://www.fastly.com/documentation/reference/http/http-headers/Fastly-Client-IP/)
* **Provider headers to avoid**
**⚠️ WARNING**\
In nearly all of these cases you are better off using XFF and reasoning about the number of proxies or their network addresses.
* Azure FrontDoor: `x-azure-clientip` -- trivially spoofable as it is the leftmost-ish XFF ([docs](https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol))
```clojure
;; Clients connect to your application server *only* through and *directly* through Cloudflare
(strategy/single-ip-header-strategy "cf-connecting-ip")
;; Clients connect to your application server *only* through and *directly* through Fly.io
(strategy/single-ip-header-strategy "fly-client-ip")
```
* **Example networks**
```
Client -> Cloudflare -> Your Server
Client -> nginx -> Your Server
Client -> Load Balancer -> Your Server
```
* **Security considerations**
* Ensure the header cannot be spoofed by clients
* Verify clients cannot bypass your proxy
* Use only the header _your_ trusted proxy sets
**⚠️ WARNING**\
Common mistakes
* Using `x-forwarded-for` with this strategy (use rightmost strategies instead)
* Not validating that clients must go through your proxy
* Changes to your topology that break a rule that was previously true
* For example, adding another proxy in the chain or exposing servers on a different interface
* Using headers that can be set by untrusted sources
---
### Rightmost Non-Private Strategy
* **When to use**\
You have multiple reverse proxies between the internet and the server, all using private IP addresses, and they append to `X-Forwarded-For` or `Forwarded` headers.
* **Why**\
In a typical private network setup, the rightmost non-private IP in the trusted forwarding chain is the real client IP. Private IPs represent your infrastructure.
```clojure
(strategy/rightmost-non-private-strategy "x-forwarded-for")
;; Using RFC 7239 Forwarded header
(strategy/rightmost-non-private-strategy "forwarded")
```
* **Example network**
```
Client -> Internet Proxy -> Private Load Balancer -> Private App Server
(1.2.3.4) (10.0.1.1) (10.0.2.1)
X-Forwarded-For: 1.2.3.4, 10.0.1.1
Result: 1.2.3.4 (rightmost non-private)
```
* **Security**\
Secure. Attackers can still spoof the leftmost entries, but not the rightmost non-private IP.
**⚠️ WARNING**\
Common Mistakes
* Do not use when your proxies have public IP addresses
* Do not use when you need to trust specific proxy IPs (use trusted range strategy instead)
---
### Rightmost Trusted Count Strategy
* **When to use**\
You know exactly how many trusted proxies append IPs to the header, and you want the IP added by the first trusted proxy.
* **Why**\
When you have a fixed, known number of trusted proxies, counting backwards gives you the IP that was added by your first trusted proxy (the client IP it saw).
```clojure
;; Two trusted proxies
(strategy/rightmost-trusted-count-strategy "x-forwarded-for" 2)
;; One trusted proxy
(strategy/rightmost-trusted-count-strategy "forwarded" 1)
```
* **Example with count=2**
```
Client -> Proxy1 -> Proxy2 -> Your Server
(adds A) (adds B)
X-Forwarded-For: A, B
With count=2: Skip 2 from right, return A
```
* **Security**\
Secure, when your proxy count is stable and known.
**⚠️ WARNING**\
Common Mistakes
* Count must exactly match your trusted proxy count
* If the count is wrong, you’ll get incorrect results or errors
* Network topology changes require updating the count on the application server
---
### Rightmost Trusted Range Strategy
* **When to use**\
You know the IP ranges of all your trusted proxies and want the rightmost IP that’s not from a trusted source.
* **Why**\
This is the most flexible strategy for complex infrastructures where you know your proxy IPs but they might change within known ranges.
```clojure
;; You have a VPC where your client-facing load balancer could be any ip address
;; inside the 10.1.1.0/24 subnet (az1) or 10.1.2.0/24 (az2)
(strategy/rightmost-trusted-range-strategy
"x-forwarded-for"
["10.1.1.0/24" "10.1.2.0/24"])
;; Including Cloudflare ranges (these are examples, do not copy them!)
(strategy/rightmost-trusted-range-strategy
"x-forwarded-for"
["173.245.48.0/20" "103.21.244.0/22" ; Example Cloudflare IPv4 ranges
"2400:cb00::/32"]) ; Example Cloudflare IPv6 range
```
* **Example**
```
Client -> Cloudflare -> Your Load Balancer -> App Server
1.2.3.4 173.245.48.1 10.0.1.1
X-Forwarded-For: 1.2.3.4, 173.245.48.1, 10.0.1.1
Trusted ranges: ["173.245.48.0/20", "10.0.0.0/8"]
Result: 1.2.3.4 (rightmost IP not in trusted ranges)
```
* **Security**\
Secure, when ranges are properly maintained.
**⚠️ WARNING**\
Common Mistakes
* Forgetting to keep the trusted ranges up to date
* Not including all possible proxy IPs
* When using a cloud provider, not using their API for current up to date ranges
---
### Chain Strategy
* **When to use**\
You have multiple possible network paths to your server and need fallback behavior.
**⚠️ WARNING**\
Do not abuse ChainStrategy to check multiple headers. There is likely only one header you should be checking, and checking more can leave you vulnerable to IP spoofing. Each strategy should represent a different network path, not multiple ways to parse the same path.
* **Why**\
Real-world deployments can have multiple possible configurations (direct connections or via CDN). Chain strategy tries each approach until one succeeds.
```clojure
;; Clients can connect via cloudflare or directly to your server
(strategy/chain-strategy
[(strategy/single-ip-header-strategy "cf-connecting-ip")
(strategy/remote-addr-strategy)])
;; Multiple fallback levels
(strategy/chain-strategy
[(strategy/rightmost-trusted-range-strategy "x-forwarded-for" ["10.0.0.0/8"])
(strategy/rightmost-non-private-strategy "x-forwarded-for")
(strategy/remote-addr-strategy)])
```
* **Example use cases**
* Development vs production environments
* Gradual migration between proxy setups
* Handling both CDN and direct traffic
---
### Leftmost Non-Private Strategy
**⚠️ WARNING**\
DO NOT USE UNLESS YOU REALLY KNOW WHAT YOU ARE DOING. The leftmost IP can be trivially spoofed by clients.
* **When to use**\
Rarely recommended. Only when you specifically need the IP allegedly closest to the original client (knowing full well that it can be spoofed).
* **Why**\
This gives you the "apparent" client IP but offers _no security_ against spoofing.
```clojure
(strategy/leftmost-non-private-strategy "x-forwarded-for")
(strategy/leftmost-non-private-strategy "forwarded")
```
* **Example**
```
X-Forwarded-For: 1.2.3.4, 2.3.4.5, 192.168.1.1
Result: 1.2.3.4 (leftmost non-private)
```
* **Valid use cases**
* Debugging or logging where you want the "claimed" client IP
* Analytics where approximate location matters more than accuracy
## Testing Your Configuration
Before deploying features that rely on the client IP address, deploy this
middleware with logging into your production network and verify your strategy
works correctly:
1. Test with expected traffic: Ensure you get the right IP for normal requests
2. Test spoofing attempts: Verify that fake headers are ignored, you can spoof headers easily with `curl -H "X-Forwarded-For: 1.2.3.4" ...`
3. Monitor for empty results: The middleware returns `nil` when a failure occurs, this could indicate an attack or a configuration problem.
## Common Pitfalls
1. Using multiple headers: Never chain strategies that check different headers from the same request
2. Wrong header choice: For example, using `x-forwarded-for` when your proxy sets `x-real-ip`
3. Ignoring network changes: Proxy counts or ranges change but your app configuration doesn’t
4. Development vs production: Different strategies needed for different environments
5. Not validating proxy control: Assuming headers can’t be spoofed when they can, don’t just trust the hyperscalers or commercial CDNs, _verify_.
Remember: _the right strategy depends entirely on your specific network configuration_. When in doubt, analyze real traffic in a real network setting.
## ol.client-ip.cidr
# ol.client-ip.cidr
## cidr-parts
```clojure
(cidr-parts cidr)
```
Parse an ip network string into its prefix and signifcant bits
`cidr` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1)
Returns a vector of [^InetAddress prefix ^Integer bits]
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/cidr.clj#L26-L39)
---
## contains?
```clojure
(contains? cidr ip)
(contains? cidr-ip cidr-mask ip)
```
Check if the given CIDR contains the given IP address.
Arguments:
cidr - CIDR notation string (e.g. "192.168.0.0/24" or "2001:db8::/32")
ip - IP address to check (can be InetAddress or string)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/cidr.clj#L41-L61)
---
## reserved-ranges
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/cidr.clj#L66-L93)
---
## reserved?
```clojure
(reserved? ip)
```
Check if an IP is in a reserved range (loopback or private network).
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/cidr.clj#L95-L98)
## ol.client-ip.core
# ol.client-ip.core
Core implementation of the client-ip library for extracting client IPs from forwarding headers.
The client-ip library is designed to accurately determine the original client IP address
from HTTP forwarding headers in Ring applications. It uses a strategy-based approach
where different strategies handle different network configurations.
## Main Features
* Strategy-based IP detection for different network setups
* Processes IPs according to the chosen strategy to prevent spoofing
* Supports all common headers: X-Forwarded-For, X-Real-IP, Forwarded (RFC 7239), etc.
## Usage
The main entry point is the `wrap-client-ip` middleware function that requires
a strategy and adds the `:ol/client-ip` key to the Ring request map:
```clojure
(require '[ol.client-ip.core :refer [wrap-client-ip]]
'[ol.client-ip.strategy :as strategy])
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/rightmost-non-private-strategy "x-forwarded-for")})
;; other middleware
))
```
You can also use `from-request` to extract client IPs directly from requests:
```clojure
(from-request request (strategy/rightmost-trusted-count-strategy "x-forwarded-for" 2))
;; => "203.0.113.195"
```
See the strategy documentation for more details on choosing the right strategy.
## from-request
```clojure
(from-request request strategy)
```
Extract the client IP from a Ring request using the specified strategy.
This is the core function for determining client IPs. It takes a Ring request
and a strategy instance, then uses the strategy to determine the client IP
from the request headers and remote address.
Refer to ns docs and the [`wrap-client-ip`](#wrap-client-ip) docstring for more usage information.
Returns the client InetAddress , or nil if no client IP can be determined.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/core.clj#L43-L58)
---
## wrap-client-ip
```clojure
(wrap-client-ip handler options)
```
Ring middleware that adds the client IP to the request map using strategies.
This middleware extracts the original client IP address using the specified
strategy and adds it to the request map as `:ol/client-ip`. The strategy determines
how headers are processed and which IP is considered the client IP.
## Options
The options map must contain:
* `:strategy` - A strategy instance (required)
## Strategy Selection
Choose the strategy that matches your network configuration:
* `RemoteAddrStrategy` - Direct connections (no reverse proxy)
* `SingleIPHeaderStrategy` - Single trusted reverse proxy with single IP headers
* `RightmostNonPrivateStrategy` - Multiple proxies, all with private IPs
* `RightmostTrustedCountStrategy` - Fixed number of trusted proxies
* `RightmostTrustedRangeStrategy` - Known trusted proxy IP ranges
* `ChainStrategy` - Try multiple strategies with fallback
## Examples
```clojure
;; Single reverse proxy with X-Real-IP
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/single-ip-header-strategy "x-real-ip")})
;; other middleware
))
;; Multiple proxies with private IPs
(def app
(-> handler
(wrap-client-ip {:strategy (strategy/rightmost-non-private-strategy "x-forwarded-for")})
;; other middleware
))
;; Chain strategy with fallback
(def app
(-> handler
(wrap-client-ip {:strategy
(strategy/chain-strategy
[(strategy/single-ip-header-strategy "x-real-ip")
(strategy/remote-addr-strategy)])})
;; other middleware
))
```
The middleware adds the `:ol/client-ip` key to the request map, containing the determined
client InetAddress or nil.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/core.clj#L60-L121)
## ol.client-ip.ip
# ol.client-ip.ip
IP address utilities for conversion and classification.
This namespace provides functions for working with IP addresses,
particularly for converting between string, InetAddress, and numeric
representations. It follows RFC 5952 for IPv6 address formatting and
provides utilities for type checking and serialization.
**📌 NOTE**\
None of the functions in this namespace accept ip addreses input as strings.
Use [`ol.client-ip.parse-ip/from-string`](api/ol-client-ip-parse-ip.adoc#from-string) to convert strings to InetAddress objects.
The key operations available are:
* Type checking with `ipv4?` and `ipv6?`
* String formatting with `format-ip`
* Numeric conversion with `->numeric` and `numeric->`
These functions are designed to work with the `java.net.InetAddress`
class hierarchy and its subclasses `Inet4Address` and `Inet6Address`.
Refer to [`ol.client-ip.parse-ip`](api/ol-client-ip-parse-ip.adoc) and [`ol.client-ip.cidr`](api/ol-client-ip-cidr.adoc) for more useful functions.
## ipv4?
```clojure
(ipv4? ip)
```
Checks if an IP address is an IPv4 address.
This function determines if the given IP address is an instance of
`java.net.Inet4Address`.
Arguments:
ip - An IP address object (InetAddress)
Returns:
`true` if the IP address is IPv4, `false` otherwise
Examples:
```clojure
(ipv4? (parse-ip/from-string "192.168.1.1")) ;; => true
(ipv4? (parse-ip/from-string "::1")) ;; => false
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L28-L46)
---
## ipv6?
```clojure
(ipv6? ip)
```
Checks if an IP address is an IPv6 address.
This function determines if the given IP address is an instance of
`java.net.Inet6Address`.
Arguments:
ip - An IP address object (InetAddress)
Returns:
`true` if the IP address is IPv6, `false` otherwise
Examples:
```clojure
(ipv6? (parse-ip/from-string "2001:db8::1")) ;; => true
(ipv6? (parse-ip/from-string "192.168.1.1")) ;; => false
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L48-L66)
---
## format-ipv6
```clojure
(format-ipv6 ip)
```
Converts an IPv6 address to its canonical string representation.
This function formats IPv6 addresses according to RFC 5952, which includes:
- Using lowercase hexadecimal digits
- Compressing the longest run of consecutive zero fields with ::
- Not using :: to compress a single zero field
- Including scope IDs with % delimiter for link-local addresses
Arguments:
ip - An IPv6 address object (Inet6Address)
Returns:
A string representation of the IPv6 address formatted according to RFC 5952
Examples:
```clojure
(format-ipv6 (parse-ip/from-string "2001:db8:0:0:0:0:0:1"))
;; => "2001:db8::1"
(format-ipv6 (parse-ip/from-string "fe80::1%eth0"))
;; => "fe80::1%eth0"
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L121-L152)
---
## format-ip
```clojure
(format-ip ip)
```
Converts an IP address to its canonical string representation.
This function formats IP addresses according to standard conventions:
- IPv4 addresses use dotted decimal notation (e.g., "192.168.0.1")
- IPv6 addresses follow RFC 5952 with compressed notation using ::
for the longest run of zeros, and embedded IPv4 addresses where appropriate
The implementation handles scope IDs for IPv6 link-local addresses
and properly compresses IPv6 addresses according to the standard rules.
Arguments:
ip - An IP address object (InetAddress)
Returns:
A string representation of the IP address
Throws:
IllegalArgumentException - if the input is not a valid InetAddress
Examples:
```clojure
(format-ip (parse-ip/from-string "192.168.1.1")) ;; => "192.168.1.1"
(format-ip (parse-ip/from-string "2001:db8::1")) ;; => "2001:db8::1"
(format-ip (parse-ip/from-string "::ffff:1.2.3.4")) ;; => "::ffff:1.2.3.4"
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L154-L185)
---
## ->numeric
```clojure
(->numeric ip)
```
Converts an IP address to its numeric representation as a BigInteger.
This function takes an InetAddress (either IPv4 or IPv6) and returns
a BigInteger representing the address. The BigInteger can be used for
IP address arithmetic, comparison, or storage.
Arguments:
ip - An IP address object (InetAddress)
Returns:
A BigInteger representing the IP address
Examples:
```clojure
(->numeric (parse-ip/from-string "192.0.2.1"))
;; => 3221225985
(->numeric (parse-ip/from-string "2001:db8::1"))
;; => 42540766411282592856903984951653826561
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L187-L209)
---
## numeric->
```clojure
(numeric-> address version)
```
Converts a BigInteger to an InetAddress.
This function takes a numeric representation of an IP address as a BigInteger
and converts it back to an InetAddress object. The `ipv6?` flag determines
whether to create an IPv4 or IPv6 address.
Arguments:
address - A BigInteger representing the IP address
version - One of :v6 or :v4 indicating whether this is an IPv6 address
or an IPv4 address
Returns:
An InetAddress object representing the numeric address, or nil
Throws:
ExceptionInfo - If the BigInteger is negative or too large for the
specified address type
Examples:
```clojure
;; Convert back to IPv4
(numeric-> (BigInteger. "3221226113") :v4)
;; => #object[java.net.Inet4Address 0x578d1c5 "/192.0.2.129"]
;; Convert back to IPv6
(numeric-> (BigInteger. "42540766411282592856903984951653826561") :v6)
;; => #object[java.net.Inet6Address 0x14832c23 "/2001:db8:0:0:0:0:0:1"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L211-L267)
---
## numeric-v6->
```clojure
(numeric-v6-> address)
```
Converts a BigInteger to an IPv6 address.
This is a convenience wrapper around `numeric->` that automatically
sets the `ipv6?` flag to true.
Arguments:
address - A BigInteger representing the IPv6 address
Returns:
An Inet6Address object or nil if the input is invalid
See also: <<numeric--GT-,`numeric->`>>
Examples:
```clojure
(numeric-v6-> (BigInteger. "42540766411282592856903984951653826561"))
;; => #object[java.net.Inet6Address 0x42668812 "/2001:db8:0:0:0:0:0:1"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L269-L292)
---
## numeric-v4->
```clojure
(numeric-v4-> address)
```
Converts a BigInteger to an IPv4 address.
Arguments:
address - A BigInteger representing the IPv4 address
Returns:
An Inet4Address object or nil if the input is invalid
See also: <<numeric--GT-,`numeric->`>>
Examples:
```clojure
(numeric-v4-> (BigInteger. "3221226113"))
;; => #object[java.net.Inet4Address 0x6b88aeff "/192.0.2.129"]
```
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/ip.clj#L294-L314)
## ol.client-ip.parse-ip
# ol.client-ip.parse-ip
Parse a string representation of an IPv4 or IPv6 address into an InetAddress instance.
Unlike `InetAddress/getByName`, the functions in this namespace never cause
DNS services to be accessed. This avoids potentially blocking/side-effecting
network calls that can occur when using the JDK’s built-in methods to parse IP
addresses.
This implementation is inspired by Google Guava’s InetAddresses class, which
provides similar functionality in Java. This code focuses on strict validation
of IP address formats according to relevant RFCs.
Features:
- Non-blocking IP address parsing with pure functions
- Strict RFC-compliant validation
- Support for all IPv4 formats
- Support for all IPv6 formats (including compressed notation and embedded IPv4)
- Support for IPv6 scope identifiers
The main entry point is `parse-ip`, which takes a string and returns an
InetAddress instance or nil if the input is an invalid ip address literal.
## bytes->inet-address
```clojure
(bytes->inet-address addr)
(bytes->inet-address addr scope)
```
Convert a byte array into an InetAddress.
Args:
addr: the raw 4-byte or 16-byte IP address in big-endian order
scope: optional scope identifier for IPv6 addresses
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/parse_ip.clj#L231-L256)
---
## from-string
```clojure
(from-string ip-string)
```
Returns the InetAddress having the given string representation or nil otherwise.
This function parses IP address strings without performing DNS lookups, making it
suitable for environments where DNS lookups would cause unwanted blocking or side effects.
It supports:
- IPv4 addresses in dotted decimal format (e.g., "192.168.1.1")
- IPv6 addresses in hex format with optional compression (e.g., "2001:db8::1")
- IPv6 addresses with scope IDs (e.g., "fe80::1%eth0" or "fe80::1%1")
- IPv6 addresses with embedded IPv4 (e.g., "::ffff:192.168.1.1")
If the input is already an InetAddress, it is returned unchanged.
Args:
ip-string: A string representing an IP address or an InetAddress
Returns:
An InetAddress object, or nil if the input couldn’t be parsed
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/parse_ip.clj#L258-L290)
## ol.client-ip.protocols
# ol.client-ip.protocols
## ClientIPStrategy
Protocol for determining client IP from headers and remote address.
Returns InetAddress or nil if no valid client ip is found.
Implementations should:
* Be thread-safe and reusable across requests
* Validate configuration at creation time (throw on invalid config)
_protocol_
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/protocols.clj#L5-L21)
### client-ip
```clojure
(client-ip this headers remote-addr)
```
Extract the client IP from request headers and remote address.
Args:
headers: Ring-style headers map (lowercase keys)
remote-addr: String from Ring request :remote-addr
Returns:
InetAddress or nil if no valid client IP found.
## ol.client-ip.strategy
# ol.client-ip.strategy
Strategy implementations for determining client IP addresses.
This namespace provides various strategy implementations for extracting the
real client IP from HTTP headers and connection information.
Each strategy is designed for specific network configurations:
* `RemoteAddrStrategy` - Direct connections (no reverse proxy)
* `SingleIPHeaderStrategy` - Single trusted reverse proxy with single IP headers
* `RightmostNonPrivateStrategy` - Multiple proxies, all with private IPs
* `LeftmostNonPrivateStrategy` - Get IP closest to original client (not secure)
* `RightmostTrustedCountStrategy` - Fixed number of trusted proxies
* `RightmostTrustedRangeStrategy` - Known trusted proxy IP ranges
* `ChainStrategy` - Try multiple strategies in order
Strategies are created once and reused across requests. They are thread-safe
and designed to fail fast on configuration errors while being resilient to
malformed input during request processing.
## parse-forwarded
```clojure
(parse-forwarded header-value)
```
Parse a Forwarded header value according to RFC 7239.
Returns a sequence of InetAddress objects, or an empty sequence if none are valid.
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L119-L130)
---
## split-host-zone
```clojure
(split-host-zone ip-string)
```
Split IPv6 zone from IP address.
IPv6 addresses can include zone identifiers (scope IDs) separated by '%'.
For example: 'fe80::1%eth0' or 'fe80::1%1'
Args:
ip-string: String IP address that may contain a zone identifier
Returns:
Vector of [ip zone] where zone is the zone identifier string,
or [ip nil] if no zone is present.
Examples:
(split-host-zone "192.168.1.1") => ["192.168.1.1" nil]
(split-host-zone "fe80::1%eth0") => ["fe80::1" "eth0"]
(split-host-zone "fe80::1%1") => ["fe80::1" "1"]
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L150-L171)
---
## remote-addr-strategy
```clojure
(remote-addr-strategy)
```
Create a strategy that uses the direct client socket IP.
This strategy extracts the IP address from the request’s :remote-addr field,
which represents the direct client socket connection. Use this when your
server accepts direct connections from clients (not behind a reverse proxy).
The strategy strips any port information and validates the IP is not
zero/unspecified (0.0.0.0 or ::).
Returns:
RemoteAddrStrategy instance
Example:
(remote-addr-strategy)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L184-L200)
---
## single-ip-header-strategy
```clojure
(single-ip-header-strategy header-name)
```
Create a strategy that extracts IP from headers containing a single IP address.
This strategy is designed for headers like X-Real-IP, CF-Connecting-IP,
True-Client-IP, etc. that contain only one IP address. Use this when you
have a single trusted reverse proxy that sets a reliable single-IP header.
The strategy validates that the header name is not X-Forwarded-For or
Forwarded, as these headers can contain multiple IPs and should use
different strategies.
Args:
header-name: String name of the header to check (case-insensitive)
Returns:
SingleIPHeaderStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to multi-IP headers
Examples:
(single-ip-header-strategy "x-real-ip")
(single-ip-header-strategy "cf-connecting-ip")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L208-L235)
---
## rightmost-non-private-strategy
```clojure
(rightmost-non-private-strategy header-name)
```
Create a strategy that gets the rightmost non-private IP from forwarding headers.
This strategy processes IPs in reverse order (rightmost first) and returns
the first IP that is not in a private/reserved range. Use this when all
your reverse proxies have private IP addresses, so the rightmost non-private
IP should be the real client IP.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
Returns:
RightmostNonPrivateStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to single-IP headers
Examples:
(rightmost-non-private-strategy "x-forwarded-for")
(rightmost-non-private-strategy "forwarded")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L250-L278)
---
## leftmost-non-private-strategy
```clojure
(leftmost-non-private-strategy header-name)
```
Create a strategy that gets the leftmost non-private IP from forwarding headers.
This strategy processes IPs in forward order (leftmost first) and returns
the first IP that is not in a private/reserved range. This gets the IP
closest to the original client.
⚠️ ***WARNING: NOT FOR SECURITY USE*** ⚠️
This strategy is easily spoofable since clients can add arbitrary IPs to
the beginning of forwarding headers. Use only when security is not a concern
and you want the IP closest to the original client.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
Returns:
LeftmostNonPrivateStrategy instance
Throws:
ExceptionInfo if header-name is invalid or refers to single-IP headers
Examples:
(leftmost-non-private-strategy "x-forwarded-for")
(leftmost-non-private-strategy "forwarded")
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L293-L325)
---
## rightmost-trusted-count-strategy
```clojure
(rightmost-trusted-count-strategy header-name trusted-count)
```
Create a strategy that returns IP at specific position based on known proxy count.
This strategy is for when you have a fixed number of trusted proxies and want
to get the IP at the specific position that represents the original client.
Given N trusted proxies, the client IP should be at position -(N+1) from the end.
For example, with 2 trusted proxies and header 'client, proxy1, proxy2, proxy3':
- Total IPs: 4
- Skip last 2 (trusted): positions 2,3
- Return IP at position 1 (proxy1)
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
trusted-count: Number of trusted proxies (must be > 0)
Returns:
RightmostTrustedCountStrategy instance
Throws:
ExceptionInfo if header-name is invalid or trusted-count <= 0
Examples:
(rightmost-trusted-count-strategy "x-forwarded-for" 1)
(rightmost-trusted-count-strategy "forwarded" 2)
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L338-L371)
---
## rightmost-trusted-range-strategy
```clojure
(rightmost-trusted-range-strategy header-name trusted-ranges)
```
Create a strategy that returns rightmost IP not in trusted ranges.
This strategy processes IPs in reverse order (rightmost first) and returns
the first IP that is not within any of the specified trusted IP ranges.
Use this when you know the specific IP ranges of your trusted proxies.
The strategy supports X-Forwarded-For and Forwarded headers that contain
comma-separated IP lists.
Args:
header-name: String name of the header to check (case-insensitive)
Should be "x-forwarded-for" or "forwarded"
trusted-ranges: Collection of CIDR ranges (strings) that represent trusted proxies
Each range should be a valid CIDR notation like "192.168.1.0/24"
Returns:
RightmostTrustedRangeStrategy instance
Throws:
ExceptionInfo if header-name is invalid or trusted-ranges is empty/invalid
Examples:
(rightmost-trusted-range-strategy "x-forwarded-for" ["10.0.0.0/8" "192.168.0.0/16"])
(rightmost-trusted-range-strategy "forwarded" ["172.16.0.0/12"])
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L386-L424)
---
## chain-strategy
```clojure
(chain-strategy strategies)
```
Create a strategy that tries multiple strategies in order until one succeeds.
This strategy allows fallback scenarios where you want to try different
IP detection methods in priority order. Each strategy is tried in sequence
until one returns a non-empty result.
Common use case: Try a single-IP header first, then fall back to remote address.
Args:
strategies: Vector of strategy instances to try in order
Must contain at least one strategy
Returns:
ChainStrategy instance
Throws:
ExceptionInfo if strategies is empty or contains invalid strategies
Examples:
(chain-strategy [(single-ip-header-strategy "x-real-ip")
(remote-addr-strategy)])
(chain-strategy [(rightmost-non-private-strategy "x-forwarded-for")
(single-ip-header-strategy "x-real-ip")
(remote-addr-strategy)])
[source,window=_blank](https://github.com/outskirtslabs/client-ip/blob/main/src/ol/client_ip/strategy.clj#L435-L470)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
## `v0.1.1` (2025-08-18)
Sorry, this is my first from-scratch clojars release, and I’m struggling
to get the metadata working.
There are no changes.
## `v0.1.0` (2025-08-18)
This is the first public release of this codebase under the
`ol.client-ip` moniker.
Variations of this code has been used in production for a long time, but
nonetheless I want to be cautious so we’re starting with sub-1.0
versioning.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
## ol.client-ip
# ol.client-ip
> A 0-dependency ring middleware for determining a request’s real client IP address from HTTP headers




`X-Forwarded-For` and other client IP headers are [often used incorrectly](https://adam-p.ca/blog/2022/03/x-forwarded-for/), resulting in bugs and security vulnerabilities. This library provides strategies for extracting the correct client IP based on your network configuration.
It is based on the golang reference implementation [realclientip/realclientip-go](https://github.com/realclientip/realclientip-go).
Quick feature list:
* ring middleware determining the client’s IP address
* 0 dependency IP address string parsing with guaranteed no trips to the hosts' DNS services, which can block and timeout (unlike Java’s `InetAddress/getByName`)
* rightmost-ish strategies support the `X-Forwarded-For` and `Forwarded` (RFC 7239) headers
* IPv6 zone identifiers support
Note that there is no dependency on ring, the public API could also be used for pedestal or sieppari-style interceptors.
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/client-ip {:mvn/version "0.1.1"}}}
;; Leiningen
[com.outskirtslabs/client-ip "0.1.1"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.client-ip.core :as client-ip]
[ol.client-ip.strategy :as strategy]))
;; Simple case: behind a trusted proxy that sets X-Real-IP
(def app
(-> handler
(client-ip/wrap-client-ip
{:strategy (strategy/single-ip-header-strategy "x-real-ip")})))
;; The client IP is now available in the request
(defn handler [request]
(let [client-ip (:ol/client-ip request)]
{:status 200
:body (str "Your IP is: " client-ip)}))
```
For detailed guidance on choosing strategies, see [Usage Guide](usage.adoc).
Choosing the wrong strategy can result in IP address spoofing security vulnerabilities.
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.client-ip/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.client-ip/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/client-ip/issues)
## Recommended Reading
You think it is an easy question:
> I have an HTTP application, I just want to know the IP address of my client.
But who is the client?
> The computer on the other end of the network connection?
But which network connection? The one connected to your HTTP application is probably a reverse proxy or load balancer.
> Well I mean the "user’s IP address"
It ain’t so easy kid.
There are many good articles on the internet that discuss the perils and pitfalls of trying to answer this deceptively simple question.
You _should_ read one or two of them to get an idea of the complexity in this space. Libraries, like this one, _cannot_ hide the complexity from you, there is no abstraction nor encapsulation nor "default best practice".
Below are some of those good articles:
* MDN: [X-Forwarded-For: Security and privacy concerns](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#security_and_privacy_concerns)
* [The perils of the "real" client IP](https://adam-p.ca/blog/2022/03/x-forwarded-for/) ([archive link](https://web.archive.org/web/20250416042714/https://adam-p.ca/blog/2022/03/x-forwarded-for/))
* [Rails IP Spoofing Vulnerabilities and Protection](https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/) ([archive link](https://web.archive.org/web/20250421121810/https://www.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/))
* [ol.client-ip: A Clojure Library to Prevent IP Spoofing](https://casey.link/blog/client-ip-ring-middleware/)
## Security
See [here](https://github.com/outskirtslabs/client-ip/security/advisories) for security advisories or to report a security vulnerability.
## License
Copyright (C) 2025 Casey Link <casey@outskirtslabs.com>
Distributed under the [MIT License](https://github.com/outskirtslabs/client-ip/blob/main/LICENSE).
## Security policy
# Security policy
## Advisories
All security advisories will be posted
https://github.com/outskirtslabs/client-ip/security/advisories[on
GitHub].
## Reporting a vulnerability
Please report possible security vulnerabilities
https://github.com/outskirtslabs/client-ip/security/advisories[via
GitHub], or by emailing me at `casey@outskirtslabs.com`. You may encrypt
emails with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
— [Casey Link](https://casey.link)
## Usage Guide
# Usage Guide
This guide explains when and why to use each strategy. The choice of strategy is
critical -- _using the wrong strategy can lead to IP spoofing vulnerabilities_.
## Understanding Your Network Setup
Before choosing a strategy, you need to understand your production environment’s network topology:
1. Are you behind reverse proxies? (nginx, Apache, load balancers, CDNs)
2. How many trusted proxies are there?
3. What headers do they set?
The answers determine which strategy is appropriate and secure for your setup.
## Terminology
* **socket-level IP**\
The IP address that a server or proxy observes at the TCP socket level from whatever is directly connecting to it. This is always trustworthy since it cannot be spoofed at the socket level, regardless of whether the connection is from an end user or another proxy in the chain.
* **private IP addresses**\
IP addresses reserved for private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) that are not routable on the public internet. These typically represent your internal infrastructure components.
* **reverse proxy / proxy / load balancer / CDN**\
An intermediary server that sits between clients and your application server, forwarding client requests. Examples include: nginx, Apache, caddy, AWS ALB, Cloudflare, Cloudfront, Fastly, Akamai, Fly.io, Hetzner Load Balancer.
* **trusted proxy**\
A reverse proxy under your control or operated by a service you trust, whose IP forwarding headers you can rely on. The key characteristic is that you know what headers it sets and clients cannot bypass it.
* **IP spoofing**\
The practice of sending packets with a forged source IP address, or in the context of HTTP headers, setting fake client IP values in headers like `X-Forwarded-For` to deceive the application about the request’s true origin.
* **trivially spoofable**\
Headers or IP values that can be easily faked by any client without special network access. For example, any client can send `X-Forwarded-For: 1.2.3.4` in their request.
* **XFF**\
Short for `X-Forwarded-For`, the most common HTTP header used by proxies to communicate the original client IP address through a chain of proxies. Each proxy typically appends the IP it received the request from.
* **rightmost-ish**\
A parsing strategy that extracts IP addresses from the right side of forwarding headers like `X-Forwarded-For`, typically skipping a known number of trusted proxies to find the IP added by the first trusted proxy. This is the only trustworthy approach since you control what your trusted proxies add to the header chain ([see this article](https://adam-p.ca/blog/2022/03/x-forwarded-for)).
* **leftmost-ish**\
A parsing strategy that takes the leftmost (first) IP address in forwarding headers, representing the alleged original client. This is highly untrustworthy and vulnerable to spoofing since any client can set arbitrary values at the beginning of these headers ([see this article](https://adam-p.ca/blog/2022/03/x-forwarded-for)).
## Strategy Reference
### Remote Address Strategy / No middleware at all!
* **When to use**\
Your server accepts direct connections from clients without any reverse proxies or load balancers.
* **Why**\
The socket-level remote address is the actual client IP when there are no intermediaries.
Using this library you can use the remote-addr-strategy, which is provided to be comprehensive, however you can also just use the existing [`:remote-addr`](https://github.com/ring-clojure/ring/blob/master/SPEC.md#remote-addr) key in your ring request map.
```clojure
(strategy/remote-addr-strategy)
;; OR, just fetch the remote addr from the ring request map
(:remote-addr request)
```
* **Example network**
```
Client -> Your Server
```
* **Security**\
Safe -- the remote address cannot be spoofed at the socket level.
---
### Single IP Header Strategy
* **When to use**\
You have a trusted reverse proxy that accepts connections directly from clients and sets a specific header with the client IP.
* **Why**\
Many Cloud Provider’s CDNs and load balancers products provide a single authoritative header with the real client IP. This is the most secure approach when you have such a setup.
* **Provider headers with trusted values**
* Cloudflare (everyone): `cf-connecting-ip` -- this is a socket-level IP value ([docs](https://developers.cloudflare.com/fundamentals/get-started/http-request-headers/))
* Cloudflare (enterprise): `true-client-ip` -- also a socket-level IP value, and just for Enterprise customers with backwards compatibility requirements ([docs](https://developers.cloudflare.com/fundamentals/get-started/http-request-headers/))
* Fly.io: `fly-client-ip` -- the socket ip address ([docs](https://www.fly.io/docs/networking/request-headers/#fly-client-ip))
* Azure FrontDoor: `x-azure-socketip` -- the socket IP address associated with the TCP connection that the current request originated from ([docs](https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol))
* nginx with correctly configured [`ngx_http_realip_module`](https://nginx.org/en/docs/http/ngx_http_realip_module.html)
* Do not use `proxy_set_header X-Real-IP $remote_addr;`
* **Provider headers that require extra config**
These providers offer headers that out of the box are trivially spoofable, and require extra configuration (in your provider’s management interface) to configure securely.
* Akamai: `true-client-ip` -- trivially spoofable by default, refer to [this writeup](https://adam-p.ca/blog/2022/03/x-forwarded-for/#akamai)
* Fastly: `fastly-client-ip` -- trivially spoofable, you must use vcl to configure it [fastly docs](https://www.fastly.com/documentation/reference/http/http-headers/Fastly-Client-IP/)
* **Provider headers to avoid**
**⚠️ WARNING**\
In nearly all of these cases you are better off using XFF and reasoning about the number of proxies or their network addresses.
* Azure FrontDoor: `x-azure-clientip` -- trivially spoofable as it is the leftmost-ish XFF ([docs](https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol))
```clojure
;; Clients connect to your application server *only* through and *directly* through Cloudflare
(strategy/single-ip-header-strategy "cf-connecting-ip")
;; Clients connect to your application server *only* through and *directly* through Fly.io
(strategy/single-ip-header-strategy "fly-client-ip")
```
* **Example networks**
```
Client -> Cloudflare -> Your Server
Client -> nginx -> Your Server
Client -> Load Balancer -> Your Server
```
* **Security considerations**
* Ensure the header cannot be spoofed by clients
* Verify clients cannot bypass your proxy
* Use only the header _your_ trusted proxy sets
**⚠️ WARNING**\
Common mistakes
* Using `x-forwarded-for` with this strategy (use rightmost strategies instead)
* Not validating that clients must go through your proxy
* Changes to your topology that break a rule that was previously true
* For example, adding another proxy in the chain or exposing servers on a different interface
* Using headers that can be set by untrusted sources
---
### Rightmost Non-Private Strategy
* **When to use**\
You have multiple reverse proxies between the internet and the server, all using private IP addresses, and they append to `X-Forwarded-For` or `Forwarded` headers.
* **Why**\
In a typical private network setup, the rightmost non-private IP in the trusted forwarding chain is the real client IP. Private IPs represent your infrastructure.
```clojure
(strategy/rightmost-non-private-strategy "x-forwarded-for")
;; Using RFC 7239 Forwarded header
(strategy/rightmost-non-private-strategy "forwarded")
```
* **Example network**
```
Client -> Internet Proxy -> Private Load Balancer -> Private App Server
(1.2.3.4) (10.0.1.1) (10.0.2.1)
X-Forwarded-For: 1.2.3.4, 10.0.1.1
Result: 1.2.3.4 (rightmost non-private)
```
* **Security**\
Secure. Attackers can still spoof the leftmost entries, but not the rightmost non-private IP.
**⚠️ WARNING**\
Common Mistakes
* Do not use when your proxies have public IP addresses
* Do not use when you need to trust specific proxy IPs (use trusted range strategy instead)
---
### Rightmost Trusted Count Strategy
* **When to use**\
You know exactly how many trusted proxies append IPs to the header, and you want the IP added by the first trusted proxy.
* **Why**\
When you have a fixed, known number of trusted proxies, counting backwards gives you the IP that was added by your first trusted proxy (the client IP it saw).
```clojure
;; Two trusted proxies
(strategy/rightmost-trusted-count-strategy "x-forwarded-for" 2)
;; One trusted proxy
(strategy/rightmost-trusted-count-strategy "forwarded" 1)
```
* **Example with count=2**
```
Client -> Proxy1 -> Proxy2 -> Your Server
(adds A) (adds B)
X-Forwarded-For: A, B
With count=2: Skip 2 from right, return A
```
* **Security**\
Secure, when your proxy count is stable and known.
**⚠️ WARNING**\
Common Mistakes
* Count must exactly match your trusted proxy count
* If the count is wrong, you’ll get incorrect results or errors
* Network topology changes require updating the count on the application server
---
### Rightmost Trusted Range Strategy
* **When to use**\
You know the IP ranges of all your trusted proxies and want the rightmost IP that’s not from a trusted source.
* **Why**\
This is the most flexible strategy for complex infrastructures where you know your proxy IPs but they might change within known ranges.
```clojure
;; You have a VPC where your client-facing load balancer could be any ip address
;; inside the 10.1.1.0/24 subnet (az1) or 10.1.2.0/24 (az2)
(strategy/rightmost-trusted-range-strategy
"x-forwarded-for"
["10.1.1.0/24" "10.1.2.0/24"])
;; Including Cloudflare ranges (these are examples, do not copy them!)
(strategy/rightmost-trusted-range-strategy
"x-forwarded-for"
["173.245.48.0/20" "103.21.244.0/22" ; Example Cloudflare IPv4 ranges
"2400:cb00::/32"]) ; Example Cloudflare IPv6 range
```
* **Example**
```
Client -> Cloudflare -> Your Load Balancer -> App Server
1.2.3.4 173.245.48.1 10.0.1.1
X-Forwarded-For: 1.2.3.4, 173.245.48.1, 10.0.1.1
Trusted ranges: ["173.245.48.0/20", "10.0.0.0/8"]
Result: 1.2.3.4 (rightmost IP not in trusted ranges)
```
* **Security**\
Secure, when ranges are properly maintained.
**⚠️ WARNING**\
Common Mistakes
* Forgetting to keep the trusted ranges up to date
* Not including all possible proxy IPs
* When using a cloud provider, not using their API for current up to date ranges
---
### Chain Strategy
* **When to use**\
You have multiple possible network paths to your server and need fallback behavior.
**⚠️ WARNING**\
Do not abuse ChainStrategy to check multiple headers. There is likely only one header you should be checking, and checking more can leave you vulnerable to IP spoofing. Each strategy should represent a different network path, not multiple ways to parse the same path.
* **Why**\
Real-world deployments can have multiple possible configurations (direct connections or via CDN). Chain strategy tries each approach until one succeeds.
```clojure
;; Clients can connect via cloudflare or directly to your server
(strategy/chain-strategy
[(strategy/single-ip-header-strategy "cf-connecting-ip")
(strategy/remote-addr-strategy)])
;; Multiple fallback levels
(strategy/chain-strategy
[(strategy/rightmost-trusted-range-strategy "x-forwarded-for" ["10.0.0.0/8"])
(strategy/rightmost-non-private-strategy "x-forwarded-for")
(strategy/remote-addr-strategy)])
```
* **Example use cases**
* Development vs production environments
* Gradual migration between proxy setups
* Handling both CDN and direct traffic
---
### Leftmost Non-Private Strategy
**⚠️ WARNING**\
DO NOT USE UNLESS YOU REALLY KNOW WHAT YOU ARE DOING. The leftmost IP can be trivially spoofed by clients.
* **When to use**\
Rarely recommended. Only when you specifically need the IP allegedly closest to the original client (knowing full well that it can be spoofed).
* **Why**\
This gives you the "apparent" client IP but offers _no security_ against spoofing.
```clojure
(strategy/leftmost-non-private-strategy "x-forwarded-for")
(strategy/leftmost-non-private-strategy "forwarded")
```
* **Example**
```
X-Forwarded-For: 1.2.3.4, 2.3.4.5, 192.168.1.1
Result: 1.2.3.4 (leftmost non-private)
```
* **Valid use cases**
* Debugging or logging where you want the "claimed" client IP
* Analytics where approximate location matters more than accuracy
## Testing Your Configuration
Before deploying features that rely on the client IP address, deploy this
middleware with logging into your production network and verify your strategy
works correctly:
1. Test with expected traffic: Ensure you get the right IP for normal requests
2. Test spoofing attempts: Verify that fake headers are ignored, you can spoof headers easily with `curl -H "X-Forwarded-For: 1.2.3.4" ...`
3. Monitor for empty results: The middleware returns `nil` when a failure occurs, this could indicate an attack or a configuration problem.
## Common Pitfalls
1. Using multiple headers: Never chain strategies that check different headers from the same request
2. Wrong header choice: For example, using `x-forwarded-for` when your proxy sets `x-real-ip`
3. Ignoring network changes: Proxy counts or ranges change but your app configuration doesn’t
4. Development vs production: Different strategies needed for different environments
5. Not validating proxy control: Assuming headers can’t be spoofed when they can, don’t just trust the hyperscalers or commercial CDNs, _verify_.
Remember: _the right strategy depends entirely on your specific network configuration_. When in doubt, analyze real traffic in a real network setting.
## ol.dirs
# ol.dirs
_platforms: clj, cljs_
Cross-platform directory lookup for Clojure, ClojureScript on Node, and ClojureDart.
The public API returns strings or vectors of strings and never creates directories.
* arity 0 returns the base directory
* arity 1 appends an application name
* arity 2 appends qualifier, organization, and application using platform rules
See [`config-home`](#config-home), [`data-home`](#data-home), [`state-home`](#state-home), and [`home-dir`](#home-dir).
## data-home
### clj
_platforms: clj_
```clojure
(data-home)
(data-home application)
(data-home qualifier organization application)
```
Returns the writable user data directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L14-L19)
### cljs
_platforms: cljs_
```clojure
(data-home)
(data-home application)
(data-home qualifier organization application)
```
Returns the writable user data directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L14-L19)
---
## config-home
### clj
_platforms: clj_
```clojure
(config-home)
(config-home application)
(config-home qualifier organization application)
```
Returns the writable user configuration directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L21-L26)
### cljs
_platforms: cljs_
```clojure
(config-home)
(config-home application)
(config-home qualifier organization application)
```
Returns the writable user configuration directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L21-L26)
---
## state-home
### clj
_platforms: clj_
```clojure
(state-home)
(state-home application)
(state-home qualifier organization application)
```
Returns the writable user state directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L28-L33)
### cljs
_platforms: cljs_
```clojure
(state-home)
(state-home application)
(state-home qualifier organization application)
```
Returns the writable user state directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L28-L33)
---
## data-dirs
### clj
_platforms: clj_
```clojure
(data-dirs)
(data-dirs application)
(data-dirs qualifier organization application)
```
Returns the shared data search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L35-L40)
### cljs
_platforms: cljs_
```clojure
(data-dirs)
(data-dirs application)
(data-dirs qualifier organization application)
```
Returns the shared data search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L35-L40)
---
## config-dirs
### clj
_platforms: clj_
```clojure
(config-dirs)
(config-dirs application)
(config-dirs qualifier organization application)
```
Returns the shared configuration search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L42-L47)
### cljs
_platforms: cljs_
```clojure
(config-dirs)
(config-dirs application)
(config-dirs qualifier organization application)
```
Returns the shared configuration search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L42-L47)
---
## cache-home
### clj
_platforms: clj_
```clojure
(cache-home)
(cache-home application)
(cache-home qualifier organization application)
```
Returns the writable user cache directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L49-L54)
### cljs
_platforms: cljs_
```clojure
(cache-home)
(cache-home application)
(cache-home qualifier organization application)
```
Returns the writable user cache directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L49-L54)
---
## runtime-dir
### clj
_platforms: clj_
```clojure
(runtime-dir)
(runtime-dir application)
(runtime-dir qualifier organization application)
```
Returns the runtime directory, or `nil` on platforms without one.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L56-L61)
### cljs
_platforms: cljs_
```clojure
(runtime-dir)
(runtime-dir application)
(runtime-dir qualifier organization application)
```
Returns the runtime directory, or `nil` on platforms without one.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L56-L61)
---
## executable-dir
### clj
_platforms: clj_
```clojure
(executable-dir)
(executable-dir application)
(executable-dir qualifier organization application)
```
Returns the user executable directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L63-L68)
### cljs
_platforms: cljs_
```clojure
(executable-dir)
(executable-dir application)
(executable-dir qualifier organization application)
```
Returns the user executable directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L63-L68)
---
## preference-dir
### clj
_platforms: clj_
```clojure
(preference-dir)
(preference-dir application)
(preference-dir qualifier organization application)
```
Returns the preference directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L70-L75)
### cljs
_platforms: cljs_
```clojure
(preference-dir)
(preference-dir application)
(preference-dir qualifier organization application)
```
Returns the preference directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L70-L75)
---
## home-dir
### clj
_platforms: clj_
```clojure
(home-dir)
```
Returns the user’s home directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L77-L79)
### cljs
_platforms: cljs_
```clojure
(home-dir)
```
Returns the user’s home directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L77-L79)
---
## audio-dir
### clj
_platforms: clj_
```clojure
(audio-dir)
```
Returns the user’s audio or music directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L81-L83)
### cljs
_platforms: cljs_
```clojure
(audio-dir)
```
Returns the user’s audio or music directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L81-L83)
---
## desktop-dir
### clj
_platforms: clj_
```clojure
(desktop-dir)
```
Returns the user’s desktop directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L85-L87)
### cljs
_platforms: cljs_
```clojure
(desktop-dir)
```
Returns the user’s desktop directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L85-L87)
---
## document-dir
### clj
_platforms: clj_
```clojure
(document-dir)
```
Returns the user’s documents directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L89-L91)
### cljs
_platforms: cljs_
```clojure
(document-dir)
```
Returns the user’s documents directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L89-L91)
---
## download-dir
### clj
_platforms: clj_
```clojure
(download-dir)
```
Returns the user’s downloads directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L93-L95)
### cljs
_platforms: cljs_
```clojure
(download-dir)
```
Returns the user’s downloads directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L93-L95)
---
## font-dir
### clj
_platforms: clj_
```clojure
(font-dir)
```
Returns the user’s font directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L97-L99)
### cljs
_platforms: cljs_
```clojure
(font-dir)
```
Returns the user’s font directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L97-L99)
---
## picture-dir
### clj
_platforms: clj_
```clojure
(picture-dir)
```
Returns the user’s pictures directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L101-L103)
### cljs
_platforms: cljs_
```clojure
(picture-dir)
```
Returns the user’s pictures directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L101-L103)
---
## public-dir
### clj
_platforms: clj_
```clojure
(public-dir)
```
Returns the user’s public sharing directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L105-L107)
### cljs
_platforms: cljs_
```clojure
(public-dir)
```
Returns the user’s public sharing directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L105-L107)
---
## template-dir
### clj
_platforms: clj_
```clojure
(template-dir)
```
Returns the user’s templates directory, or `nil` when unsupported or unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L109-L111)
### cljs
_platforms: cljs_
```clojure
(template-dir)
```
Returns the user’s templates directory, or `nil` when unsupported or unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L109-L111)
---
## video-dir
### clj
_platforms: clj_
```clojure
(video-dir)
```
Returns the user’s videos directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L113-L115)
### cljs
_platforms: cljs_
```clojure
(video-dir)
```
Returns the user’s videos directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L113-L115)
---
## state-dir
### clj
_platforms: clj_
```clojure
(state-dir)
(state-dir application)
(state-dir qualifier organization application)
```
Alias of [`state-home`](#state-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L117-L122)
### cljs
_platforms: cljs_
```clojure
(state-dir)
(state-dir application)
(state-dir qualifier organization application)
```
Alias of [`state-home`](#state-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L117-L122)
---
## config-dir
### clj
_platforms: clj_
```clojure
(config-dir)
(config-dir application)
(config-dir qualifier organization application)
```
Alias of [`config-home`](#config-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L124-L129)
### cljs
_platforms: cljs_
```clojure
(config-dir)
(config-dir application)
(config-dir qualifier organization application)
```
Alias of [`config-home`](#config-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/v0.1.x/src/ol/dirs.cljc#L124-L129)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## ++[++UNRELEASED++]++
## `v0.1.0` (2026-03-12)
This is the first public release of this codebase under the `ol.dirs` name.
This release establishes the first published cross-platform directory discovery API for Clojure, ClojureScript on Node, and ClojureDart.
Private variations of the clj/JVM code has been used in production for a awhile, but nonetheless I want to be cautious so we’re starting with sub-1.0 versioning.
## ol.dirs
# ol.dirs
> A 0-dependency implementation of XDG base directories, systemd unit directories, Windows Known Folder paths, and macOS standard directories for Clojure, ClojureScript, and ClojureDart.



[(https://img.shields.io/clojars/v/com.outskirtslabs/dirs.svg)]
`ol.dirs` returns string paths or vectors of string paths. It does not create directories, does not check whether they exist, and carries no runtime dependencies.
## Support Matrix
| Runtime | Linux | macOS | Windows |
| --- | --- | --- | --- |
| Clojure | yes | yes | yes |
| ClojureScript on Node | yes | yes | yes |
| ClojureDart | yes | yes | yes |
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/dirs {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/dirs "0.1.0"]
```
## Quick Start
```clojure
(require '[ol.dirs :as dirs])
(dirs/config-home)
;; => "/home/alice/.config"
(dirs/config-home "My App")
;; => "/home/alice/.config/my-app"
(dirs/config-home "org" "Acme Corp" "My App")
;; => Linux: "/home/alice/.config/my-app"
;; => macOS: "/Users/Alice/Library/Application Support/org.Acme-Corp.My-App"
;; => Windows: "C:\\Users\\Alice\\AppData\\Roaming\\Acme Corp\\My App"
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.dirs/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.dirs/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/dirs/issues)
## API Notes
These functions support app-specific arities. Arity `0` returns the base directory, arity `1` appends an application name, and arity `3` appends a platform-specific qualifier, organization, and application path. The concrete path shapes are shown below in the `App Path Rules` table and in the example above.
`data-home`, `config-home`, `state-home`, `cache-home`, `runtime-dir`, `executable-dir`, `preference-dir`, `state-dir`, and `config-dir` all support those same arities.
`data-dirs` and `config-dirs` do too, but return vectors and append the application path to each search root.
### App Path Rules
| Form | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `("My App")` | `my-app` | `My-App` | `My App` |
| `("org" "Acme Corp" "My App")` | `my-app` | `org.Acme-Corp.My-App` | `Acme Corp\My App` |
### Base Directories
| Function | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `home-dir` | `$HOME` | `$HOME` | `%USERPROFILE%` |
| `data-home` | `$XDG_DATA_HOME` or `$HOME/.local/share` | `$HOME/Library/Application Support` | `%AppData%` |
| `config-home` | `$XDG_CONFIG_HOME` or `$HOME/.config` | `$HOME/Library/Application Support` | `%AppData%` |
| `state-home` | `$XDG_STATE_HOME` or `$HOME/.local/state` | `$HOME/Library/Application Support` | `%LocalAppData%` |
| `cache-home` | `$XDG_CACHE_HOME` or `$HOME/.cache` | `$HOME/Library/Caches` | `%LocalAppData%` |
| `data-dirs` | `$XDG_DATA_DIRS` or `/usr/local/share`, `/usr/share` | `/Library/Application Support` | `%ProgramData%` |
| `config-dirs` | `$XDG_CONFIG_DIRS` or `/etc/xdg` | `/Library/Application Support` | `%ProgramData%` |
| `preference-dir` | same as `config-home` | `$HOME/Library/Preferences` | `%AppData%` |
| `runtime-dir` | `$XDG_RUNTIME_DIR` or `nil` | `nil` | `nil` |
| `executable-dir` | `$XDG_BIN_HOME` or `$XDG_DATA_HOME/../bin` | `nil` | `nil` |
On Linux, trusted systemd units can override `config-home`, `state-home`, and `cache-home` via `CONFIGURATION_DIRECTORY`, `STATE_DIRECTORY`, and `CACHE_DIRECTORY` as documented in [`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html).
### User-Facing Directories
| Function | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `audio-dir` | `XDG_MUSIC_DIR` from `user-dirs.dirs` | `$HOME/Music` | Music Known Folder |
| `desktop-dir` | `XDG_DESKTOP_DIR` from `user-dirs.dirs` | `$HOME/Desktop` | Desktop Known Folder |
| `document-dir` | `XDG_DOCUMENTS_DIR` from `user-dirs.dirs` | `$HOME/Documents` | Documents Known Folder |
| `download-dir` | `XDG_DOWNLOAD_DIR` from `user-dirs.dirs` | `$HOME/Downloads` | Downloads Known Folder |
| `font-dir` | `$XDG_DATA_HOME/fonts` | `$HOME/Library/Fonts` | `nil` |
| `picture-dir` | `XDG_PICTURES_DIR` from `user-dirs.dirs` | `$HOME/Pictures` | Pictures Known Folder |
| `public-dir` | `XDG_PUBLICSHARE_DIR` from `user-dirs.dirs` | `$HOME/Public` | Public Known Folder |
| `template-dir` | `XDG_TEMPLATES_DIR` from `user-dirs.dirs` | `nil` | Templates Known Folder |
| `video-dir` | `XDG_VIDEOS_DIR` from `user-dirs.dirs` | `$HOME/Movies` | Videos Known Folder |
## Nil Semantics
These functions may return `nil` by design:
* `runtime-dir` and `executable-dir` on non-Linux platforms
* `font-dir` on Windows
* `template-dir` on macOS
* Linux user-facing directories when `user-dirs.dirs` is missing, unreadable, disabled, or malformed
## Development
```bash
bb test:clj
bb test:cljs
bb test:cljd
bb test
```
The CLJD workspace commands live under [`dart/`](dart/), because the Dart toolchain emits generated files that are easier to keep isolated there.
## License
Copyright (C) 2026 Casey Link
Distributed under the [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
## Security policy
# Security policy
## Advisories
All security advisories for `ol.dirs` will be posted [on GitHub](https://github.com/outskirtslabs/dirs/security/advisories).
## Reporting a vulnerability
Please report possible security vulnerabilities [via GitHub](https://github.com/outskirtslabs/dirs/security/advisories), or by emailing me at `casey@outskirtslabs.com`. You may encrypt email with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
[Casey Link](https://casey.link)
## ol.dirs
# ol.dirs
_platforms: clj, cljs_
Cross-platform directory lookup for Clojure, ClojureScript on Node, and ClojureDart.
The public API returns strings or vectors of strings and never creates directories.
* arity 0 returns the base directory
* arity 1 appends an application name
* arity 2 appends qualifier, organization, and application using platform rules
See [`config-home`](#config-home), [`data-home`](#data-home), [`state-home`](#state-home), and [`home-dir`](#home-dir).
## data-home
### clj
_platforms: clj_
```clojure
(data-home)
(data-home application)
(data-home qualifier organization application)
```
Returns the writable user data directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L14-L19)
### cljs
_platforms: cljs_
```clojure
(data-home)
(data-home application)
(data-home qualifier organization application)
```
Returns the writable user data directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L14-L19)
---
## config-home
### clj
_platforms: clj_
```clojure
(config-home)
(config-home application)
(config-home qualifier organization application)
```
Returns the writable user configuration directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L21-L26)
### cljs
_platforms: cljs_
```clojure
(config-home)
(config-home application)
(config-home qualifier organization application)
```
Returns the writable user configuration directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L21-L26)
---
## state-home
### clj
_platforms: clj_
```clojure
(state-home)
(state-home application)
(state-home qualifier organization application)
```
Returns the writable user state directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L28-L33)
### cljs
_platforms: cljs_
```clojure
(state-home)
(state-home application)
(state-home qualifier organization application)
```
Returns the writable user state directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L28-L33)
---
## data-dirs
### clj
_platforms: clj_
```clojure
(data-dirs)
(data-dirs application)
(data-dirs qualifier organization application)
```
Returns the shared data search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L35-L40)
### cljs
_platforms: cljs_
```clojure
(data-dirs)
(data-dirs application)
(data-dirs qualifier organization application)
```
Returns the shared data search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L35-L40)
---
## config-dirs
### clj
_platforms: clj_
```clojure
(config-dirs)
(config-dirs application)
(config-dirs qualifier organization application)
```
Returns the shared configuration search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L42-L47)
### cljs
_platforms: cljs_
```clojure
(config-dirs)
(config-dirs application)
(config-dirs qualifier organization application)
```
Returns the shared configuration search roots as a vector of strings.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L42-L47)
---
## cache-home
### clj
_platforms: clj_
```clojure
(cache-home)
(cache-home application)
(cache-home qualifier organization application)
```
Returns the writable user cache directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L49-L54)
### cljs
_platforms: cljs_
```clojure
(cache-home)
(cache-home application)
(cache-home qualifier organization application)
```
Returns the writable user cache directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L49-L54)
---
## runtime-dir
### clj
_platforms: clj_
```clojure
(runtime-dir)
(runtime-dir application)
(runtime-dir qualifier organization application)
```
Returns the runtime directory, or `nil` on platforms without one.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L56-L61)
### cljs
_platforms: cljs_
```clojure
(runtime-dir)
(runtime-dir application)
(runtime-dir qualifier organization application)
```
Returns the runtime directory, or `nil` on platforms without one.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L56-L61)
---
## executable-dir
### clj
_platforms: clj_
```clojure
(executable-dir)
(executable-dir application)
(executable-dir qualifier organization application)
```
Returns the user executable directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L63-L68)
### cljs
_platforms: cljs_
```clojure
(executable-dir)
(executable-dir application)
(executable-dir qualifier organization application)
```
Returns the user executable directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L63-L68)
---
## preference-dir
### clj
_platforms: clj_
```clojure
(preference-dir)
(preference-dir application)
(preference-dir qualifier organization application)
```
Returns the preference directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L70-L75)
### cljs
_platforms: cljs_
```clojure
(preference-dir)
(preference-dir application)
(preference-dir qualifier organization application)
```
Returns the preference directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L70-L75)
---
## home-dir
### clj
_platforms: clj_
```clojure
(home-dir)
```
Returns the user’s home directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L77-L79)
### cljs
_platforms: cljs_
```clojure
(home-dir)
```
Returns the user’s home directory.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L77-L79)
---
## audio-dir
### clj
_platforms: clj_
```clojure
(audio-dir)
```
Returns the user’s audio or music directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L81-L83)
### cljs
_platforms: cljs_
```clojure
(audio-dir)
```
Returns the user’s audio or music directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L81-L83)
---
## desktop-dir
### clj
_platforms: clj_
```clojure
(desktop-dir)
```
Returns the user’s desktop directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L85-L87)
### cljs
_platforms: cljs_
```clojure
(desktop-dir)
```
Returns the user’s desktop directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L85-L87)
---
## document-dir
### clj
_platforms: clj_
```clojure
(document-dir)
```
Returns the user’s documents directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L89-L91)
### cljs
_platforms: cljs_
```clojure
(document-dir)
```
Returns the user’s documents directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L89-L91)
---
## download-dir
### clj
_platforms: clj_
```clojure
(download-dir)
```
Returns the user’s downloads directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L93-L95)
### cljs
_platforms: cljs_
```clojure
(download-dir)
```
Returns the user’s downloads directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L93-L95)
---
## font-dir
### clj
_platforms: clj_
```clojure
(font-dir)
```
Returns the user’s font directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L97-L99)
### cljs
_platforms: cljs_
```clojure
(font-dir)
```
Returns the user’s font directory, or `nil` when unsupported.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L97-L99)
---
## picture-dir
### clj
_platforms: clj_
```clojure
(picture-dir)
```
Returns the user’s pictures directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L101-L103)
### cljs
_platforms: cljs_
```clojure
(picture-dir)
```
Returns the user’s pictures directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L101-L103)
---
## public-dir
### clj
_platforms: clj_
```clojure
(public-dir)
```
Returns the user’s public sharing directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L105-L107)
### cljs
_platforms: cljs_
```clojure
(public-dir)
```
Returns the user’s public sharing directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L105-L107)
---
## template-dir
### clj
_platforms: clj_
```clojure
(template-dir)
```
Returns the user’s templates directory, or `nil` when unsupported or unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L109-L111)
### cljs
_platforms: cljs_
```clojure
(template-dir)
```
Returns the user’s templates directory, or `nil` when unsupported or unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L109-L111)
---
## video-dir
### clj
_platforms: clj_
```clojure
(video-dir)
```
Returns the user’s videos directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L113-L115)
### cljs
_platforms: cljs_
```clojure
(video-dir)
```
Returns the user’s videos directory, or `nil` when unresolved.
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L113-L115)
---
## state-dir
### clj
_platforms: clj_
```clojure
(state-dir)
(state-dir application)
(state-dir qualifier organization application)
```
Alias of [`state-home`](#state-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L117-L122)
### cljs
_platforms: cljs_
```clojure
(state-dir)
(state-dir application)
(state-dir qualifier organization application)
```
Alias of [`state-home`](#state-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L117-L122)
---
## config-dir
### clj
_platforms: clj_
```clojure
(config-dir)
(config-dir application)
(config-dir qualifier organization application)
```
Alias of [`config-home`](#config-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L124-L129)
### cljs
_platforms: cljs_
```clojure
(config-dir)
(config-dir application)
(config-dir qualifier organization application)
```
Alias of [`config-home`](#config-home).
[source,window=_blank](https://github.com/outskirtslabs/dirs/blob/main/src/ol/dirs.cljc#L124-L129)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## ++[++UNRELEASED++]++
## `v0.1.0` (2026-03-12)
This is the first public release of this codebase under the `ol.dirs` name.
This release establishes the first published cross-platform directory discovery API for Clojure, ClojureScript on Node, and ClojureDart.
Private variations of the clj/JVM code has been used in production for a awhile, but nonetheless I want to be cautious so we’re starting with sub-1.0 versioning.
## ol.dirs
# ol.dirs
> A 0-dependency implementation of XDG base directories, systemd unit directories, Windows Known Folder paths, and macOS standard directories for Clojure, ClojureScript, and ClojureDart.



[(https://img.shields.io/clojars/v/com.outskirtslabs/dirs.svg)]
`ol.dirs` returns string paths or vectors of string paths. It does not create directories, does not check whether they exist, and carries no runtime dependencies.
## Support Matrix
| Runtime | Linux | macOS | Windows |
| --- | --- | --- | --- |
| Clojure | yes | yes | yes |
| ClojureScript on Node | yes | yes | yes |
| ClojureDart | yes | yes | yes |
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/dirs {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/dirs "0.1.0"]
```
## Quick Start
```clojure
(require '[ol.dirs :as dirs])
(dirs/config-home)
;; => "/home/alice/.config"
(dirs/config-home "My App")
;; => "/home/alice/.config/my-app"
(dirs/config-home "org" "Acme Corp" "My App")
;; => Linux: "/home/alice/.config/my-app"
;; => macOS: "/Users/Alice/Library/Application Support/org.Acme-Corp.My-App"
;; => Windows: "C:\\Users\\Alice\\AppData\\Roaming\\Acme Corp\\My App"
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.dirs/next/)
* [API Reference](https://docs.outskirtslabs.com/ol.dirs/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/dirs/issues)
## API Notes
These functions support app-specific arities. Arity `0` returns the base directory, arity `1` appends an application name, and arity `3` appends a platform-specific qualifier, organization, and application path. The concrete path shapes are shown below in the `App Path Rules` table and in the example above.
`data-home`, `config-home`, `state-home`, `cache-home`, `runtime-dir`, `executable-dir`, `preference-dir`, `state-dir`, and `config-dir` all support those same arities.
`data-dirs` and `config-dirs` do too, but return vectors and append the application path to each search root.
### App Path Rules
| Form | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `("My App")` | `my-app` | `My-App` | `My App` |
| `("org" "Acme Corp" "My App")` | `my-app` | `org.Acme-Corp.My-App` | `Acme Corp\My App` |
### Base Directories
| Function | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `home-dir` | `$HOME` | `$HOME` | `%USERPROFILE%` |
| `data-home` | `$XDG_DATA_HOME` or `$HOME/.local/share` | `$HOME/Library/Application Support` | `%AppData%` |
| `config-home` | `$XDG_CONFIG_HOME` or `$HOME/.config` | `$HOME/Library/Application Support` | `%AppData%` |
| `state-home` | `$XDG_STATE_HOME` or `$HOME/.local/state` | `$HOME/Library/Application Support` | `%LocalAppData%` |
| `cache-home` | `$XDG_CACHE_HOME` or `$HOME/.cache` | `$HOME/Library/Caches` | `%LocalAppData%` |
| `data-dirs` | `$XDG_DATA_DIRS` or `/usr/local/share`, `/usr/share` | `/Library/Application Support` | `%ProgramData%` |
| `config-dirs` | `$XDG_CONFIG_DIRS` or `/etc/xdg` | `/Library/Application Support` | `%ProgramData%` |
| `preference-dir` | same as `config-home` | `$HOME/Library/Preferences` | `%AppData%` |
| `runtime-dir` | `$XDG_RUNTIME_DIR` or `nil` | `nil` | `nil` |
| `executable-dir` | `$XDG_BIN_HOME` or `$XDG_DATA_HOME/../bin` | `nil` | `nil` |
On Linux, trusted systemd units can override `config-home`, `state-home`, and `cache-home` via `CONFIGURATION_DIRECTORY`, `STATE_DIRECTORY`, and `CACHE_DIRECTORY` as documented in [`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html).
### User-Facing Directories
| Function | Linux | macOS | Windows |
| --- | --- | --- | --- |
| `audio-dir` | `XDG_MUSIC_DIR` from `user-dirs.dirs` | `$HOME/Music` | Music Known Folder |
| `desktop-dir` | `XDG_DESKTOP_DIR` from `user-dirs.dirs` | `$HOME/Desktop` | Desktop Known Folder |
| `document-dir` | `XDG_DOCUMENTS_DIR` from `user-dirs.dirs` | `$HOME/Documents` | Documents Known Folder |
| `download-dir` | `XDG_DOWNLOAD_DIR` from `user-dirs.dirs` | `$HOME/Downloads` | Downloads Known Folder |
| `font-dir` | `$XDG_DATA_HOME/fonts` | `$HOME/Library/Fonts` | `nil` |
| `picture-dir` | `XDG_PICTURES_DIR` from `user-dirs.dirs` | `$HOME/Pictures` | Pictures Known Folder |
| `public-dir` | `XDG_PUBLICSHARE_DIR` from `user-dirs.dirs` | `$HOME/Public` | Public Known Folder |
| `template-dir` | `XDG_TEMPLATES_DIR` from `user-dirs.dirs` | `nil` | Templates Known Folder |
| `video-dir` | `XDG_VIDEOS_DIR` from `user-dirs.dirs` | `$HOME/Movies` | Videos Known Folder |
## Nil Semantics
These functions may return `nil` by design:
* `runtime-dir` and `executable-dir` on non-Linux platforms
* `font-dir` on Windows
* `template-dir` on macOS
* Linux user-facing directories when `user-dirs.dirs` is missing, unreadable, disabled, or malformed
## Development
```bash
bb test:clj
bb test:cljs
bb test:cljd
bb test
```
The CLJD workspace commands live under [`dart/`](dart/), because the Dart toolchain emits generated files that are easier to keep isolated there.
## License
Copyright (C) 2026 Casey Link
Distributed under the [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
## Security policy
# Security policy
## Advisories
All security advisories for `ol.dirs` will be posted [on GitHub](https://github.com/outskirtslabs/dirs/security/advisories).
## Reporting a vulnerability
Please report possible security vulnerabilities [via GitHub](https://github.com/outskirtslabs/dirs/security/advisories), or by emailing me at `casey@outskirtslabs.com`. You may encrypt email with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
[Casey Link](https://casey.link)
## ol.llx.agent.driver
# ol.llx.agent.driver
_platforms: clj, cljs_
## start!
### clj
_platforms: clj_
```clojure
(start! env)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/driver.cljc#L277-L285)
### cljs
_platforms: cljs_
```clojure
(start! env)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/driver.cljc#L277-L285)
---
## run
### clj
_platforms: clj_
```clojure
(run env command)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/driver.cljc#L287-L294)
### cljs
_platforms: cljs_
```clojure
(run env command)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/driver.cljc#L287-L294)
## ol.llx.agent.fx.inference
# ol.llx.agent.fx.inference
_platforms: clj, cljs_
## LlmEventStepSignal
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L15-L21)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L15-L21)
---
## LlmEventStep
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L23-L27)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L23-L27)
---
## llm-event->step
### clj
_platforms: clj_
```clojure
(llm-event->step run-id model partial event)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L73-L159)
### cljs
_platforms: cljs_
```clojure
(llm-event->step run-id model partial event)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L73-L159)
---
## fx-call-llm
### clj
_platforms: clj_
```clojure
(fx-call-llm {:keys [state_ convert-to-llm transform-context stream-fn get-api-key] :as env} effect)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L188-L236)
### cljs
_platforms: cljs_
```clojure
(fx-call-llm {:keys [state_ convert-to-llm transform-context stream-fn get-api-key] :as env} effect)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/inference.cljc#L188-L236)
## ol.llx.agent.fx.tools
# ol.llx.agent.fx.tools
_platforms: clj, cljs_
## fx-execute-tool
### clj
_platforms: clj_
```clojure
(fx-execute-tool {:keys [state_]} effect)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/tools.cljc#L55-L100)
### cljs
_platforms: cljs_
```clojure
(fx-execute-tool {:keys [state_]} effect)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx/tools.cljc#L55-L100)
## ol.llx.agent.fx
# ol.llx.agent.fx
_platforms: clj, cljs_
Effect interpreter for the agent runtime.
The pure state machine in `ol.llx.agent.loop` emits inert effect maps.
This namespace interprets those maps into concrete side effects.
Effect categories:
Fire-and-forget — synchronous, produce no signals.
- `:emit-event` — publish an event to subscribers
- `:reject` — report invalid operation state
Signal-producing — async, return a channel of signals.
- `:call-llm` — perform inference and stream `llm-*` signals
- `:execute-tool` — execute a tool and emit `tool-*` signals
Signal-producing effects return promesa CSP channels consumed by
`ol.llx.agent.driver/run`.
## execute-fx
### clj
_platforms: clj_
```clojure
(execute-fx env effect)
```
Interpret a single effect description. Dispatches on `::type`.
Fire-and-forget effects (`:emit-event`, `:reject`):
Execute the side effect synchronously. Return `nil`.
Signal-producing effects (`:call-llm`, `:execute-tool`):
Start the async work and return a promesa CSP channel that will
emit signal maps. The channel closes when the effect is complete.
The driver consumes the channel and steps each signal through
`loop/step`. The interpreter must not call `step` or mutate the
state atom.
`env` carries runtime dependencies:
- `:state_` — atom holding current agent state (read-only for fx)
- `:events-mx>` — event multiplexer write endpoint
- `:convert-to-llm` — `(fn [messages])` transform to LLM messages
- `:transform-context` — `(fn [messages abort-signal])` optional context pruning
- `:stream-fn` — `(fn [model context opts])` LLM streaming fn
- `:abort-signal` — abort token for cancellation
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx.cljc#L36-L66)
### cljs
_platforms: cljs_
```clojure
(execute-fx env effect)
```
Interpret a single effect description. Dispatches on `::type`.
Fire-and-forget effects (`:emit-event`, `:reject`):
Execute the side effect synchronously. Return `nil`.
Signal-producing effects (`:call-llm`, `:execute-tool`):
Start the async work and return a promesa CSP channel that will
emit signal maps. The channel closes when the effect is complete.
The driver consumes the channel and steps each signal through
`loop/step`. The interpreter must not call `step` or mutate the
state atom.
`env` carries runtime dependencies:
- `:state_` — atom holding current agent state (read-only for fx)
- `:events-mx>` — event multiplexer write endpoint
- `:convert-to-llm` — `(fn [messages])` transform to LLM messages
- `:transform-context` — `(fn [messages abort-signal])` optional context pruning
- `:stream-fn` — `(fn [model context opts])` LLM streaming fn
- `:abort-signal` — abort token for cancellation
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/fx.cljc#L36-L66)
## ol.llx.agent.loop
# ol.llx.agent.loop
_platforms: clj, cljs_
## empty-queue
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L5-L7)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L5-L7)
---
## handle-command
### clj
_platforms: clj_
```clojure
(handle-command state cmd)
```
Pure command handler. Takes state + command, returns `[state' signals]`.
Handles global state mutations directly and translates FSM-driving
commands into signals for the graph.
Commands that only mutate state (e.g. `:ol.llx.agent.command/steer`) return an empty
signals vector. Commands that drive the FSM (e.g. `:ol.llx.agent.command/prompt`)
return one or more signals to be fed into the transition functions.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L137-L223)
### cljs
_platforms: cljs_
```clojure
(handle-command state cmd)
```
Pure command handler. Takes state + command, returns `[state' signals]`.
Handles global state mutations directly and translates FSM-driving
commands into signals for the graph.
Commands that only mutate state (e.g. `:ol.llx.agent.command/steer`) return an empty
signals vector. Commands that drive the FSM (e.g. `:ol.llx.agent.command/prompt`)
return one or more signals to be fed into the transition functions.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L137-L223)
---
## idle-transition
### clj
_platforms: clj_
```clojure
(idle-transition state msg)
```
Pure transition from ::idle state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L225-L254)
### cljs
_platforms: cljs_
```clojure
(idle-transition state msg)
```
Pure transition from ::idle state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L225-L254)
---
## streaming-transition
### clj
_platforms: clj_
```clojure
(streaming-transition state msg)
```
Pure transition from ::streaming state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L256-L308)
### cljs
_platforms: cljs_
```clojure
(streaming-transition state msg)
```
Pure transition from ::streaming state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L256-L308)
---
## tool-executing-transition
### clj
_platforms: clj_
```clojure
(tool-executing-transition state msg)
```
Pure transition from ::tool-executing state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L310-L334)
### cljs
_platforms: cljs_
```clojure
(tool-executing-transition state msg)
```
Pure transition from ::tool-executing state. Returns [state' effects].
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L310-L334)
---
## closed-transition
### clj
_platforms: clj_
```clojure
(closed-transition state _msg)
```
Terminal state. No transitions possible.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L336-L339)
### cljs
_platforms: cljs_
```clojure
(closed-transition state _msg)
```
Terminal state. No transitions possible.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L336-L339)
---
## route-from-idle
### clj
_platforms: clj_
```clojure
(route-from-idle state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L341-L342)
### cljs
_platforms: cljs_
```clojure
(route-from-idle state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L341-L342)
---
## route-from-streaming
### clj
_platforms: clj_
```clojure
(route-from-streaming state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L344-L345)
### cljs
_platforms: cljs_
```clojure
(route-from-streaming state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L344-L345)
---
## route-from-tool-executing
### clj
_platforms: clj_
```clojure
(route-from-tool-executing state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L347-L353)
### cljs
_platforms: cljs_
```clojure
(route-from-tool-executing state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L347-L353)
---
## command?
### clj
_platforms: clj_
```clojure
(command? input)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L355-L357)
### cljs
_platforms: cljs_
```clojure
(command? input)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L355-L357)
---
## graph
### clj
_platforms: clj_
Agent state machine as data.
Transitions: `(state, msg) -> [state', effects]`
Pure transition fns. Take current state and a signal, return
new state and a vector of effect descriptions to execute.
Routes: `(state') -> phase-key`
Pure routing fns. Take the post-transition state and return
the keyword of the next phase to enter.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L379-L397)
### cljs
_platforms: cljs_
Agent state machine as data.
Transitions: `(state, msg) -> [state', effects]`
Pure transition fns. Take current state and a signal, return
new state and a vector of effect descriptions to execute.
Routes: `(state') -> phase-key`
Pure routing fns. Take the post-transition state and return
the keyword of the next phase to enter.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L379-L397)
---
## step
### clj
_platforms: clj_
```clojure
(step state input)
(step g state input)
```
Pure step function. Takes state + input (command or signal), returns `[state' effects]`.
Commands are processed by `handle-command` which may produce signals.
Signals are processed by the graph transition + routing fns.
Emits `:ol.llx.agent.event/agent-end` automatically when the agent transitions from a non-idle phase back to `::idle`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L399-L417)
### cljs
_platforms: cljs_
```clojure
(step state input)
(step g state input)
```
Pure step function. Takes state + input (command or signal), returns `[state' effects]`.
Commands are processed by `handle-command` which may produce signals.
Signals are processed by the graph transition + routing fns.
Emits `:ol.llx.agent.event/agent-end` automatically when the agent transitions from a non-idle phase back to `::idle`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/loop.cljc#L399-L417)
## ol.llx.agent.schema
# ol.llx.agent.schema
_platforms: clj, cljs_
## state-atom?
### clj
_platforms: clj_
```clojure
(state-atom? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L13-L16)
### cljs
_platforms: cljs_
```clojure
(state-atom? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L13-L16)
---
## queue?
### clj
_platforms: clj_
```clojure
(queue? coll)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L18-L21)
### cljs
_platforms: cljs_
```clojure
(queue? coll)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L18-L21)
---
## multiplexer?
### clj
_platforms: clj_
```clojure
(multiplexer? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L23-L25)
### cljs
_platforms: cljs_
```clojure
(multiplexer? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L23-L25)
---
## schema-registry?
### clj
_platforms: clj_
```clojure
(schema-registry? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L27-L29)
### cljs
_platforms: cljs_
```clojure
(schema-registry? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L27-L29)
---
## canonical-message-roles
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L31-L31)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L31-L31)
---
## custom-message-role?
### clj
_platforms: clj_
```clojure
(custom-message-role? role)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L33-L36)
### cljs
_platforms: cljs_
```clojure
(custom-message-role? role)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L33-L36)
---
## message-dispatch
### clj
_platforms: clj_
```clojure
(message-dispatch message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L38-L40)
### cljs
_platforms: cljs_
```clojure
(message-dispatch message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L38-L40)
---
## schemas
### clj
_platforms: clj_
```clojure
(schemas {:keys [custom-message-schemas]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L47-L574)
### cljs
_platforms: cljs_
```clojure
(schemas {:keys [custom-message-schemas]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L47-L574)
---
## custom-schemas
### clj
_platforms: clj_
```clojure
(custom-schemas opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L576-L579)
### cljs
_platforms: cljs_
```clojure
(custom-schemas opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L576-L579)
---
## registry
### clj
_platforms: clj_
```clojure
(registry opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L581-L585)
### cljs
_platforms: cljs_
```clojure
(registry opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L581-L585)
---
## derive-active-registry
### clj
_platforms: clj_
```clojure
(derive-active-registry {:keys [schema-registry custom-message-schemas]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L587-L591)
### cljs
_platforms: cljs_
```clojure
(derive-active-registry {:keys [schema-registry custom-message-schemas]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L587-L591)
---
## validate!
### clj
_platforms: clj_
```clojure
(validate! schema-registry schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L596-L605)
### cljs
_platforms: cljs_
```clojure
(validate! schema-registry schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent/schema.cljc#L596-L605)
## ol.llx.agent
# ol.llx.agent
_platforms: clj, cljs_
Public runtime wrapper for the agent loop.
This namespace wires runtime state, effect environment dependencies,
and subscription management around `ol.llx.agent.loop` + `ol.llx.agent.fx`.
## create-initial-state
### clj
_platforms: clj_
```clojure
(create-initial-state schema-registry schema-config opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L37-L49)
### cljs
_platforms: cljs_
```clojure
(create-initial-state schema-registry schema-config opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L37-L49)
---
## default-convert-to-llm
### clj
_platforms: clj_
```clojure
(default-convert-to-llm messages)
```
Converts agent messages to LLM-compatible messages by keeping only maps
whose `:role` is one of `:user`, `:assistant`, or `:tool-result`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L51-L59)
### cljs
_platforms: cljs_
```clojure
(default-convert-to-llm messages)
```
Converts agent messages to LLM-compatible messages by keeping only maps
whose `:role` is one of `:user`, `:assistant`, or `:tool-result`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L51-L59)
---
## create-agent
### clj
_platforms: clj_
```clojure
(create-agent)
(create-agent opts)
```
Creates an agent runtime handle.
Required options:
- `:tools` vector of tools
`opts` may include:
- `:convert-to-llm` `(fn [messages])`; defaults to filtering
messages to roles `:user`, `:assistant`,
`:tool-result`
- `:transform-context` optional context transform hook
- `:stream-fn` optional stream hook; resolved at callsite
- `:schema-registry` user-owned extra Malli schemas
- `:custom-message-schemas` user-owned custom message dispatch map
- `:session-id`, `:get-api-key`, `:thinking-budgets`, `:max-retry-delay-ms`
- `:system-prompt`, `:model`, `:thinking-level`
- `:steering-mode`, `:follow-up-mode`
- `:abort-signal`
For state rehydration, use [`rehydrate-agent`](#rehydrate-agent).
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L89-L119)
### cljs
_platforms: cljs_
```clojure
(create-agent)
(create-agent opts)
```
Creates an agent runtime handle.
Required options:
- `:tools` vector of tools
`opts` may include:
- `:convert-to-llm` `(fn [messages])`; defaults to filtering
messages to roles `:user`, `:assistant`,
`:tool-result`
- `:transform-context` optional context transform hook
- `:stream-fn` optional stream hook; resolved at callsite
- `:schema-registry` user-owned extra Malli schemas
- `:custom-message-schemas` user-owned custom message dispatch map
- `:session-id`, `:get-api-key`, `:thinking-budgets`, `:max-retry-delay-ms`
- `:system-prompt`, `:model`, `:thinking-level`
- `:steering-mode`, `:follow-up-mode`
- `:abort-signal`
For state rehydration, use [`rehydrate-agent`](#rehydrate-agent).
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L89-L119)
---
## rehydrate-agent
### clj
_platforms: clj_
```clojure
(rehydrate-agent state opts)
```
Creates an agent runtime from a previously persisted state snapshot.
`state` must satisfy `:ol.llx.agent.loop/state`.
`opts` carries runtime dependencies and may include:
- `:convert-to-llm` (optional; see <>)
- `:transform-context` (optional; see <>)
- `:stream-fn` (optional; see <>)
- `:session-id`, `:get-api-key`, `:thinking-budgets`, `:max-retry-delay-ms`
* `:tools`
* `:abort-signal`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L121-L136)
### cljs
_platforms: cljs_
```clojure
(rehydrate-agent state opts)
```
Creates an agent runtime from a previously persisted state snapshot.
`state` must satisfy `:ol.llx.agent.loop/state`.
`opts` carries runtime dependencies and may include:
- `:convert-to-llm` (optional; see <>)
- `:transform-context` (optional; see <>)
- `:stream-fn` (optional; see <>)
- `:session-id`, `:get-api-key`, `:thinking-budgets`, `:max-retry-delay-ms`
* `:tools`
* `:abort-signal`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L121-L136)
---
## state
### clj
_platforms: clj_
```clojure
(state agent)
```
Returns the current agent state snapshot.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L138-L141)
### cljs
_platforms: cljs_
```clojure
(state agent)
```
Returns the current agent state snapshot.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L138-L141)
---
## subscribe
### clj
_platforms: clj_
```clojure
(subscribe agent)
(subscribe agent ch)
```
Subscribes a channel to the agent event stream.
* `(subscribe agent)` creates and returns a buffered channel.
* `(subscribe agent ch)` taps the provided channel and returns it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L147-L157)
### cljs
_platforms: cljs_
```clojure
(subscribe agent)
(subscribe agent ch)
```
Subscribes a channel to the agent event stream.
* `(subscribe agent)` creates and returns a buffered channel.
* `(subscribe agent ch)` taps the provided channel and returns it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L147-L157)
---
## unsubscribe
### clj
_platforms: clj_
```clojure
(unsubscribe agent ch)
```
Unsubscribes a channel from the agent event stream and closes it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L159-L164)
### cljs
_platforms: cljs_
```clojure
(unsubscribe agent ch)
```
Unsubscribes a channel from the agent event stream and closes it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L159-L164)
---
## prompt
### clj
_platforms: clj_
```clojure
(prompt agent messages)
```
Submits a prompt command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L182-L186)
### cljs
_platforms: cljs_
```clojure
(prompt agent messages)
```
Submits a prompt command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L182-L186)
---
## continue
### clj
_platforms: clj_
```clojure
(continue agent)
```
Submits a continue command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L188-L191)
### cljs
_platforms: cljs_
```clojure
(continue agent)
```
Submits a continue command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L188-L191)
---
## abort
### clj
_platforms: clj_
```clojure
(abort agent)
```
Submits an abort command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L193-L196)
### cljs
_platforms: cljs_
```clojure
(abort agent)
```
Submits an abort command to the agent runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L193-L196)
---
## steer
### clj
_platforms: clj_
```clojure
(steer agent messages)
```
Queues steering messages.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L198-L201)
### cljs
_platforms: cljs_
```clojure
(steer agent messages)
```
Queues steering messages.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L198-L201)
---
## follow-up
### clj
_platforms: clj_
```clojure
(follow-up agent messages)
```
Queues follow-up messages.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L203-L206)
### cljs
_platforms: cljs_
```clojure
(follow-up agent messages)
```
Queues follow-up messages.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L203-L206)
---
## wait-for-idle
### clj
_platforms: clj_
```clojure
(wait-for-idle agent)
(wait-for-idle agent max-polls)
```
Waits until the agent reaches `:ol.llx.agent.loop/idle` or `:ol.llx.agent.loop/closed`.
Resolves `true` when idle/closed, or `false` after the polling budget is
exhausted.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L208-L227)
### cljs
_platforms: cljs_
```clojure
(wait-for-idle agent)
(wait-for-idle agent max-polls)
```
Waits until the agent reaches `:ol.llx.agent.loop/idle` or `:ol.llx.agent.loop/closed`.
Resolves `true` when idle/closed, or `false` after the polling budget is
exhausted.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L208-L227)
---
## set-system-prompt
### clj
_platforms: clj_
```clojure
(set-system-prompt agent system-prompt)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L229-L232)
### cljs
_platforms: cljs_
```clojure
(set-system-prompt agent system-prompt)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L229-L232)
---
## set-model
### clj
_platforms: clj_
```clojure
(set-model agent model)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L234-L236)
### cljs
_platforms: cljs_
```clojure
(set-model agent model)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L234-L236)
---
## set-thinking-level
### clj
_platforms: clj_
```clojure
(set-thinking-level agent thinking-level)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L238-L241)
### cljs
_platforms: cljs_
```clojure
(set-thinking-level agent thinking-level)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L238-L241)
---
## set-tools
### clj
_platforms: clj_
```clojure
(set-tools agent tools)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L243-L245)
### cljs
_platforms: cljs_
```clojure
(set-tools agent tools)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L243-L245)
---
## set-schema-config
### clj
_platforms: clj_
```clojure
(set-schema-config agent schema-config-update)
```
Replaces the agent’s user-owned schema configuration.
`schema-config-update` may include `:schema-registry` and/or
`:custom-message-schemas`. `nil` for either field preserves the current
user-owned value.
This setter is accepted in any phase. It affects future validation and
future tool execution only; already queued messages and already running
tool executions keep using the registry they started with.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L257-L278)
### cljs
_platforms: cljs_
```clojure
(set-schema-config agent schema-config-update)
```
Replaces the agent’s user-owned schema configuration.
`schema-config-update` may include `:schema-registry` and/or
`:custom-message-schemas`. `nil` for either field preserves the current
user-owned value.
This setter is accepted in any phase. It affects future validation and
future tool execution only; already queued messages and already running
tool executions keep using the registry they started with.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L257-L278)
---
## set-steering-mode
### clj
_platforms: clj_
```clojure
(set-steering-mode agent mode)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L280-L282)
### cljs
_platforms: cljs_
```clojure
(set-steering-mode agent mode)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L280-L282)
---
## set-follow-up-mode
### clj
_platforms: clj_
```clojure
(set-follow-up-mode agent mode)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L284-L286)
### cljs
_platforms: cljs_
```clojure
(set-follow-up-mode agent mode)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L284-L286)
---
## replace-messages
### clj
_platforms: clj_
```clojure
(replace-messages agent messages)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L288-L291)
### cljs
_platforms: cljs_
```clojure
(replace-messages agent messages)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L288-L291)
---
## append-message
### clj
_platforms: clj_
```clojure
(append-message agent message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L293-L296)
### cljs
_platforms: cljs_
```clojure
(append-message agent message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L293-L296)
---
## clear-messages
### clj
_platforms: clj_
```clojure
(clear-messages agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L298-L300)
### cljs
_platforms: cljs_
```clojure
(clear-messages agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L298-L300)
---
## clear-steering-queue
### clj
_platforms: clj_
```clojure
(clear-steering-queue agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L302-L304)
### cljs
_platforms: cljs_
```clojure
(clear-steering-queue agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L302-L304)
---
## clear-follow-up-queue
### clj
_platforms: clj_
```clojure
(clear-follow-up-queue agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L306-L308)
### cljs
_platforms: cljs_
```clojure
(clear-follow-up-queue agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L306-L308)
---
## clear-all-queues
### clj
_platforms: clj_
```clojure
(clear-all-queues agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L310-L312)
### cljs
_platforms: cljs_
```clojure
(clear-all-queues agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L310-L312)
---
## reset
### clj
_platforms: clj_
```clojure
(reset agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L314-L316)
### cljs
_platforms: cljs_
```clojure
(reset agent)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L314-L316)
---
## close
### clj
_platforms: clj_
```clojure
(close agent)
```
Closes the runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L318-L321)
### cljs
_platforms: cljs_
```clojure
(close agent)
```
Closes the runtime.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/agent.cljc#L318-L321)
## ol.llx.ai.errors
# ol.llx.ai.errors
_platforms: clj, cljs_
## llx-error?
### clj
_platforms: clj_
```clojure
(llx-error? ex)
```
Returns true when `ex` is a structured LLX error exception.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L5-L8)
### cljs
_platforms: cljs_
```clojure
(llx-error? ex)
```
Returns true when `ex` is a structured LLX error exception.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L5-L8)
---
## recoverable?
### clj
_platforms: clj_
```clojure
(recoverable? ex)
```
Returns true when `ex` is classified as recoverable.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L10-L13)
### cljs
_platforms: cljs_
```clojure
(recoverable? ex)
```
Returns true when `ex` is classified as recoverable.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L10-L13)
---
## rate-limit-error?
### clj
_platforms: clj_
```clojure
(rate-limit-error? ex)
```
Returns true when `ex` is a rate-limit error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L15-L18)
### cljs
_platforms: cljs_
```clojure
(rate-limit-error? ex)
```
Returns true when `ex` is a rate-limit error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L15-L18)
---
## quota-exceeded-error?
### clj
_platforms: clj_
```clojure
(quota-exceeded-error? ex)
```
Returns true when `ex` is a quota-exceeded error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L20-L23)
### cljs
_platforms: cljs_
```clojure
(quota-exceeded-error? ex)
```
Returns true when `ex` is a quota-exceeded error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L20-L23)
---
## rate-limited-error?
### clj
_platforms: clj_
```clojure
(rate-limited-error? ex)
```
Returns true when `ex` is either a rate-limit or quota-exceeded error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L25-L28)
### cljs
_platforms: cljs_
```clojure
(rate-limited-error? ex)
```
Returns true when `ex` is either a rate-limit or quota-exceeded error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L25-L28)
---
## timeout-error?
### clj
_platforms: clj_
```clojure
(timeout-error? ex)
```
Returns true when `ex` is a timeout error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L30-L33)
### cljs
_platforms: cljs_
```clojure
(timeout-error? ex)
```
Returns true when `ex` is a timeout error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L30-L33)
---
## client-error?
### clj
_platforms: clj_
```clojure
(client-error? ex)
```
Returns true when `ex` is a non-recoverable client-classified error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L35-L38)
### cljs
_platforms: cljs_
```clojure
(client-error? ex)
```
Returns true when `ex` is a non-recoverable client-classified error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L35-L38)
---
## transient-error?
### clj
_platforms: clj_
```clojure
(transient-error? ex)
```
Returns true when `ex` is a transient error eligible for retry handling.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L40-L43)
### cljs
_platforms: cljs_
```clojure
(transient-error? ex)
```
Returns true when `ex` is a transient error eligible for retry handling.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L40-L43)
---
## should-retry?
### clj
_platforms: clj_
```clojure
(should-retry? ex)
(should-retry? ex & opts)
```
Returns true when retry policy allows another attempt for `ex`.
Optional keyword arguments include `:max-retries` and `:current-retry`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L45-L52)
### cljs
_platforms: cljs_
```clojure
(should-retry? ex)
(should-retry? ex & opts)
```
Returns true when retry policy allows another attempt for `ex`.
Optional keyword arguments include `:max-retries` and `:current-retry`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L45-L52)
---
## retry-delay-ms
### clj
_platforms: clj_
```clojure
(retry-delay-ms ex retry-count)
```
Returns the retry delay in milliseconds for `ex` at `retry-count`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L54-L57)
### cljs
_platforms: cljs_
```clojure
(retry-delay-ms ex retry-count)
```
Returns the retry delay in milliseconds for `ex` at `retry-count`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/errors.cljc#L54-L57)
## ol.llx.ai.impl.adapters.anthropic-messages
# ol.llx.ai.impl.adapters.anthropic-messages
_platforms: clj, cljs_
## normalize-tool-call-id
### clj
_platforms: clj_
```clojure
(normalize-tool-call-id tool-call-id _target-model _source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L43-L49)
### cljs
_platforms: cljs_
```clojure
(normalize-tool-call-id tool-call-id _target-model _source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L43-L49)
---
## build-request
### clj
_platforms: clj_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L202-L283)
### cljs
_platforms: cljs_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L202-L283)
---
## finalize
### clj
_platforms: clj_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L315-L337)
### cljs
_platforms: cljs_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L315-L337)
---
## decode-event
### clj
_platforms: clj_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L364-L481)
### cljs
_platforms: cljs_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L364-L481)
---
## normalize-error
### clj
_platforms: clj_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L483-L508)
### cljs
_platforms: cljs_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L483-L508)
---
## handle-open-stream-response
### clj
_platforms: clj_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L510-L535)
### cljs
_platforms: cljs_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L510-L535)
---
## open-stream
### clj
_platforms: clj_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L537-L542)
### cljs
_platforms: cljs_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L537-L542)
---
## adapter
### clj
_platforms: clj_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L544-L555)
### cljs
_platforms: cljs_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/anthropic_messages.cljc#L544-L555)
## ol.llx.ai.impl.adapters.common
# ol.llx.ai.impl.adapters.common
_platforms: clj, cljs_
## trim-trailing-slash
### clj
_platforms: clj_
```clojure
(trim-trailing-slash s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L3-L9)
### cljs
_platforms: cljs_
```clojure
(trim-trailing-slash s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L3-L9)
---
## parse-json-safe
### clj
_platforms: clj_
```clojure
(parse-json-safe env s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L11-L15)
### cljs
_platforms: cljs_
```clojure
(parse-json-safe env s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L11-L15)
---
## parse-json-lenient
### clj
_platforms: clj_
```clojure
(parse-json-lenient env s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L17-L23)
### cljs
_platforms: cljs_
```clojure
(parse-json-lenient env s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L17-L23)
---
## empty-usage
### clj
_platforms: clj_
```clojure
(empty-usage)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L25-L36)
### cljs
_platforms: cljs_
```clojure
(empty-usage)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/common.cljc#L25-L36)
## ol.llx.ai.impl.adapters.google-generative-ai
# ol.llx.ai.impl.adapters.google-generative-ai
_platforms: clj, cljs_
## normalize-tool-call-id
### clj
_platforms: clj_
```clojure
(normalize-tool-call-id tool-call-id target-model _source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L71-L78)
### cljs
_platforms: cljs_
```clojure
(normalize-tool-call-id tool-call-id target-model _source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L71-L78)
---
## build-request
### clj
_platforms: clj_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L306-L365)
### cljs
_platforms: cljs_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L306-L365)
---
## decode-event
### clj
_platforms: clj_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L483-L508)
### cljs
_platforms: cljs_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L483-L508)
---
## finalize
### clj
_platforms: clj_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L562-L573)
### cljs
_platforms: cljs_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L562-L573)
---
## normalize-error
### clj
_platforms: clj_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L575-L600)
### cljs
_platforms: cljs_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L575-L600)
---
## handle-open-stream-response
### clj
_platforms: clj_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L602-L627)
### cljs
_platforms: cljs_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L602-L627)
---
## open-stream
### clj
_platforms: clj_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L629-L634)
### cljs
_platforms: cljs_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L629-L634)
---
## adapter
### clj
_platforms: clj_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L636-L646)
### cljs
_platforms: cljs_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/google_generative_ai.cljc#L636-L646)
## ol.llx.ai.impl.adapters.openai-codex-responses
# ol.llx.ai.impl.adapters.openai-codex-responses
_platforms: clj, cljs_
## build-request
### clj
_platforms: clj_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L59-L85)
### cljs
_platforms: cljs_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L59-L85)
---
## decode-event
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L87-L88)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L87-L88)
---
## finalize
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L90-L91)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L90-L91)
---
## normalize-error
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L93-L94)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L93-L94)
---
## open-stream
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L96-L97)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L96-L97)
---
## normalize-tool-call-id
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L99-L100)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L99-L100)
---
## adapter
### clj
_platforms: clj_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L102-L113)
### cljs
_platforms: cljs_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_codex_responses.cljc#L102-L113)
## ol.llx.ai.impl.adapters.openai-completions
# ol.llx.ai.impl.adapters.openai-completions
_platforms: clj, cljs_
## build-request
### clj
_platforms: clj_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L246-L317)
### cljs
_platforms: cljs_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L246-L317)
---
## response->assistant-message
### clj
_platforms: clj_
```clojure
(response->assistant-message env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L357-L380)
### cljs
_platforms: cljs_
```clojure
(response->assistant-message env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L357-L380)
---
## init-stream-state
### clj
_platforms: clj_
```clojure
(init-stream-state env model)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L382-L394)
### cljs
_platforms: cljs_
```clojure
(init-stream-state env model)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L382-L394)
---
## decode-event
### clj
_platforms: clj_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L596-L606)
### cljs
_platforms: cljs_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L596-L606)
---
## finalize
### clj
_platforms: clj_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L608-L619)
### cljs
_platforms: cljs_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L608-L619)
---
## normalize-error
### clj
_platforms: clj_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L621-L646)
### cljs
_platforms: cljs_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L621-L646)
---
## handle-open-stream-response
### clj
_platforms: clj_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L648-L673)
### cljs
_platforms: cljs_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L648-L673)
---
## open-stream
### clj
_platforms: clj_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L675-L680)
### cljs
_platforms: cljs_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L675-L680)
---
## normalize-tool-call-id
### clj
_platforms: clj_
```clojure
(normalize-tool-call-id tool-call-id target-model source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L742-L761)
### cljs
_platforms: cljs_
```clojure
(normalize-tool-call-id tool-call-id target-model source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L742-L761)
---
## adapter
### clj
_platforms: clj_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L763-L774)
### cljs
_platforms: cljs_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_completions.cljc#L763-L774)
## ol.llx.ai.impl.adapters.openai-responses
# ol.llx.ai.impl.adapters.openai-responses
_platforms: clj, cljs_
## normalize-tool-call-id
### clj
_platforms: clj_
```clojure
(normalize-tool-call-id tool-call-id target-model source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L86-L100)
### cljs
_platforms: cljs_
```clojure
(normalize-tool-call-id tool-call-id target-model source-assistant-message)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L86-L100)
---
## build-request
### clj
_platforms: clj_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L294-L347)
### cljs
_platforms: cljs_
```clojure
(build-request env model context opts)
(build-request env model context opts stream?)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L294-L347)
---
## decode-event
### clj
_platforms: clj_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L373-L585)
### cljs
_platforms: cljs_
```clojure
(decode-event env state raw-chunk)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L373-L585)
---
## finalize
### clj
_platforms: clj_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L649-L657)
### cljs
_platforms: cljs_
```clojure
(finalize env state-or-response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L649-L657)
---
## normalize-error
### clj
_platforms: clj_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L659-L684)
### cljs
_platforms: cljs_
```clojure
(normalize-error env ex partial-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L659-L684)
---
## handle-open-stream-response
### clj
_platforms: clj_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L686-L711)
### cljs
_platforms: cljs_
```clojure
(handle-open-stream-response env model response)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L686-L711)
---
## open-stream
### clj
_platforms: clj_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L713-L718)
### cljs
_platforms: cljs_
```clojure
(open-stream env _model request-map)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L713-L718)
---
## adapter
### clj
_platforms: clj_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L720-L731)
### cljs
_platforms: cljs_
```clojure
(adapter)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/adapters/openai_responses.cljc#L720-L731)
## ol.llx.ai.impl.client.event-stream
# ol.llx.ai.impl.client.event-stream
_platforms: clj, cljs_
Shared stream runtime orchestration for all hosts.
## Why
Host runtimes should differ only in transport concerns (how bytes arrive, how
cancellation reaches the underlying request). Event decoding, lifecycle
emission, finalize behavior, and terminal error handling must stay identical.
## What
This namespace owns the shared stream loop:
- opens a provider stream with retry policy
- emits canonical `:start` / delta / `:done` events
- converts runtime failures into canonical terminal error events
- coordinates cancellation across producer/source and consumer/output channel
## How
Hosts inject two hooks:
- `:open-stream!` to acquire the transport stream response
- `:start-source!` to push provider payloads onto `payload-ch`
The shared loop consumes payload/control channels with Promesa CSP, applies
adapter decode/finalize functions, and emits canonical events to `out`.
## Related Namespaces
* [`ol.llx.ai.impl.client.jvm`](api/ol-llx-ai-impl-client-jvm.adoc)
* [`ol.llx.ai.impl.client.node`](api/ol-llx-ai-impl-client-node.adoc)
* [`ol.llx.ai.impl.client`](api/ol-llx-ai-impl-client.adoc)
## await!
### clj
_platforms: clj_
```clojure
(await! x)
(await! x duration default-on-timeout)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L48-L62)
### cljs
_platforms: cljs_
```clojure
(await! x)
(await! x _duration default-on-timeout)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L65-L71)
---
## open-stream-with-retries*
### clj
_platforms: clj_
```clojure
(open-stream-with-retries* {:keys [adapter env model request request-opts]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L73-L92)
### cljs
_platforms: cljs_
```clojure
(open-stream-with-retries* {:keys [adapter env model request request-opts]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L73-L92)
---
## emit-events*
### clj
_platforms: clj_
```clojure
(emit-events* {:keys [emit! cancel-fn events]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L94-L108)
### cljs
_platforms: cljs_
```clojure
(emit-events* {:keys [emit! cancel-fn events]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L94-L108)
---
## emit-start*
### clj
_platforms: clj_
```clojure
(emit-start* {:keys [emit! cancel-fn env model]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L110-L124)
### cljs
_platforms: cljs_
```clojure
(emit-start* {:keys [emit! cancel-fn env model]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L110-L124)
---
## decode-payload-step
### clj
_platforms: clj_
```clojure
(decode-payload-step {:keys [adapter env model state item-index payload]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L126-L145)
### cljs
_platforms: cljs_
```clojure
(decode-payload-step {:keys [adapter env model state item-index payload]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L126-L145)
---
## process-payload-step*
### clj
_platforms: clj_
```clojure
(process-payload-step* {:keys [adapter env model state* emit! cancel-fn]} item-index payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L147-L163)
### cljs
_platforms: cljs_
```clojure
(process-payload-step* {:keys [adapter env model state* emit! cancel-fn]} item-index payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L147-L163)
---
## finalize-step
### clj
_platforms: clj_
```clojure
(finalize-step {:keys [adapter env state]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L165-L172)
### cljs
_platforms: cljs_
```clojure
(finalize-step {:keys [adapter env state]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L165-L172)
---
## finalize-stream*
### clj
_platforms: clj_
```clojure
(finalize-stream* {:keys [adapter env model state* emit! cancel-fn]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L174-L194)
### cljs
_platforms: cljs_
```clojure
(finalize-stream* {:keys [adapter env model state* emit! cancel-fn]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L174-L194)
---
## emit-terminal-error*
### clj
_platforms: clj_
```clojure
(emit-terminal-error* {:keys [adapter env model state* emit!]} stream-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L196-L199)
### cljs
_platforms: cljs_
```clojure
(emit-terminal-error* {:keys [adapter env model state* emit!]} stream-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L196-L199)
---
## payload-msg
### clj
_platforms: clj_
```clojure
(payload-msg payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L201-L203)
### cljs
_platforms: cljs_
```clojure
(payload-msg payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L201-L203)
---
## error-msg
### clj
_platforms: clj_
```clojure
(error-msg ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L205-L207)
### cljs
_platforms: cljs_
```clojure
(error-msg ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L205-L207)
---
## run-stream!
### clj
_platforms: clj_
```clojure
(run-stream! {:keys [adapter env model request out state* cancel! start-source! open-stream!] :as args})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L220-L330)
### cljs
_platforms: cljs_
```clojure
(run-stream! {:keys [adapter env model request out state* cancel! start-source! open-stream!] :as args})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/event_stream.cljc#L220-L330)
## ol.llx.ai.impl.client.jvm
# ol.llx.ai.impl.client.jvm
## start-jvm-source!
```clojure
(start-jvm-source! {:keys [response payload-ch cancelled?] :as input})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/jvm.clj#L62-L88)
---
## open-stream-jvm!
```clojure
(open-stream-jvm! {:keys [adapter env model request request-opts]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/jvm.clj#L90-L106)
---
## run-stream!
```clojure
(run-stream! {:keys [adapter env model request request-opts] :as input})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/jvm.clj#L108-L126)
---
## default-env
```clojure
(default-env)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/jvm.clj#L128-L144)
## ol.llx.ai.impl.client.node
# ol.llx.ai.impl.client.node
## start-node-source!
```clojure
(start-node-source! {:keys [response payload-ch cancelled?] :as input})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/node.cljs#L107-L164)
---
## run-stream!
```clojure
(run-stream! {:keys [adapter env model request request-opts] :as input})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/node.cljs#L166-L216)
---
## default-env
```clojure
(default-env)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client/node.cljs#L218-L240)
## ol.llx.ai.impl.client
# ol.llx.ai.impl.client
_platforms: clj, cljs_
## default-registry
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L23-L39)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L23-L39)
---
## unified-opts->request-opts
### clj
_platforms: clj_
```clojure
(unified-opts->request-opts model unified-opts)
```
Converts unified options to provider-path request options.
See [`ol.llx.ai/complete`](api/ol-llx-ai.adoc#complete) and [`ol.llx.ai/stream`](api/ol-llx-ai.adoc#stream) for unified option semantics.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L88-L106)
### cljs
_platforms: cljs_
```clojure
(unified-opts->request-opts model unified-opts)
```
Converts unified options to provider-path request options.
See [`ol.llx.ai/complete`](api/ol-llx-ai.adoc#complete) and [`ol.llx.ai/stream`](api/ol-llx-ai.adoc#stream) for unified option semantics.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L88-L106)
---
## provider-from-host
### clj
_platforms: clj_
```clojure
(provider-from-host host)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L146-L156)
### cljs
_platforms: cljs_
```clojure
(provider-from-host host)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L146-L156)
---
## runtime-data-line->payload
### clj
_platforms: clj_
```clojure
(runtime-data-line->payload line)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L210-L215)
### cljs
_platforms: cljs_
```clojure
(runtime-data-line->payload line)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L210-L215)
---
## runtime-fallback-error-message
### clj
_platforms: clj_
```clojure
(runtime-fallback-error-message stream-ex normalize-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L217-L222)
### cljs
_platforms: cljs_
```clojure
(runtime-fallback-error-message stream-ex normalize-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L217-L222)
---
## runtime-fallback-assistant-message
### clj
_platforms: clj_
```clojure
(runtime-fallback-assistant-message env model stream-ex normalize-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L224-L239)
### cljs
_platforms: cljs_
```clojure
(runtime-fallback-assistant-message env model stream-ex normalize-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L224-L239)
---
## runtime-payload->provider-item-type
### clj
_platforms: clj_
```clojure
(runtime-payload->provider-item-type env payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L241-L248)
### cljs
_platforms: cljs_
```clojure
(runtime-payload->provider-item-type env payload)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L241-L248)
---
## runtime-split-lines
### clj
_platforms: clj_
```clojure
(runtime-split-lines buffer)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L250-L254)
### cljs
_platforms: cljs_
```clojure
(runtime-split-lines buffer)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L250-L254)
---
## runtime-terminal-error-event
### clj
_platforms: clj_
```clojure
(runtime-terminal-error-event adapter env model state* stream-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L269-L290)
### cljs
_platforms: cljs_
```clojure
(runtime-terminal-error-event adapter env model state* stream-ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L269-L290)
---
## complete*
### clj
_platforms: clj_
```clojure
(complete* env model context opts)
```
See [`ol.llx.ai/complete*`](api/ol-llx-ai.adoc#complete-STAR-)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L292-L366)
### cljs
_platforms: cljs_
```clojure
(complete* env model context opts)
```
See [`ol.llx.ai/complete*`](api/ol-llx-ai.adoc#complete-STAR-)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L292-L366)
---
## stream*
### clj
_platforms: clj_
```clojure
(stream* env model context opts)
```
See [`ol.llx.ai/stream*`](api/ol-llx-ai.adoc#stream-STAR-)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L368-L438)
### cljs
_platforms: cljs_
```clojure
(stream* env model context opts)
```
See [`ol.llx.ai/stream*`](api/ol-llx-ai.adoc#stream-STAR-)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L368-L438)
---
## stream
### clj
_platforms: clj_
```clojure
(stream env model context unified-opts)
```
See [`ol.llx.ai/stream`](api/ol-llx-ai.adoc#stream)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L440-L445)
### cljs
_platforms: cljs_
```clojure
(stream env model context unified-opts)
```
See [`ol.llx.ai/stream`](api/ol-llx-ai.adoc#stream)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L440-L445)
---
## complete
### clj
_platforms: clj_
```clojure
(complete env model context unified-opts)
```
See [`ol.llx.ai/complete`](api/ol-llx-ai.adoc#complete)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L447-L452)
### cljs
_platforms: cljs_
```clojure
(complete env model context unified-opts)
```
See [`ol.llx.ai/complete`](api/ol-llx-ai.adoc#complete)
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/client.cljc#L447-L452)
## ol.llx.ai.impl.errors
# ol.llx.ai.impl.errors
_platforms: clj, cljs_
## client-errors
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L7-L17)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L7-L17)
---
## transient-errors
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L19-L23)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L19-L23)
---
## response-errors
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L25-L27)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L25-L27)
---
## all-error-types
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L29-L30)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L29-L30)
---
## rate-limit
### clj
_platforms: clj_
```clojure
(rate-limit provider message & {:keys [http-status retry-after request-id provider-code]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L46-L55)
### cljs
_platforms: cljs_
```clojure
(rate-limit provider message & {:keys [http-status retry-after request-id provider-code]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L46-L55)
---
## server-error
### clj
_platforms: clj_
```clojure
(server-error provider message & {:keys [http-status provider-code request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L57-L65)
### cljs
_platforms: cljs_
```clojure
(server-error provider message & {:keys [http-status provider-code request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L57-L65)
---
## timeout-error
### clj
_platforms: clj_
```clojure
(timeout-error provider message & {:keys [timeout-ms request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L67-L74)
### cljs
_platforms: cljs_
```clojure
(timeout-error provider message & {:keys [timeout-ms request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L67-L74)
---
## connection-error
### clj
_platforms: clj_
```clojure
(connection-error provider message & {:keys [request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L76-L82)
### cljs
_platforms: cljs_
```clojure
(connection-error provider message & {:keys [request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L76-L82)
---
## authentication-error
### clj
_platforms: clj_
```clojure
(authentication-error provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L84-L90)
### cljs
_platforms: cljs_
```clojure
(authentication-error provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L84-L90)
---
## authorization-error
### clj
_platforms: clj_
```clojure
(authorization-error provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L92-L98)
### cljs
_platforms: cljs_
```clojure
(authorization-error provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L92-L98)
---
## invalid-request
### clj
_platforms: clj_
```clojure
(invalid-request message & {:keys [provider http-status context]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L100-L107)
### cljs
_platforms: cljs_
```clojure
(invalid-request message & {:keys [provider http-status context]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L100-L107)
---
## model-not-found
### clj
_platforms: clj_
```clojure
(model-not-found provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L109-L115)
### cljs
_platforms: cljs_
```clojure
(model-not-found provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L109-L115)
---
## quota-exceeded
### clj
_platforms: clj_
```clojure
(quota-exceeded provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L117-L123)
### cljs
_platforms: cljs_
```clojure
(quota-exceeded provider message & {:keys [http-status]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L117-L123)
---
## retry-delay-exceeded
### clj
_platforms: clj_
```clojure
(retry-delay-exceeded provider requested-delay-ms max-delay-ms & {:keys [request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L125-L141)
### cljs
_platforms: cljs_
```clojure
(retry-delay-exceeded provider requested-delay-ms max-delay-ms & {:keys [request-id]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L125-L141)
---
## content-filter
### clj
_platforms: clj_
```clojure
(content-filter provider message & {:keys [provider-code]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L143-L149)
### cljs
_platforms: cljs_
```clojure
(content-filter provider message & {:keys [provider-code]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L143-L149)
---
## invalid-response
### clj
_platforms: clj_
```clojure
(invalid-response provider message & {:keys [context]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L151-L157)
### cljs
_platforms: cljs_
```clojure
(invalid-response provider message & {:keys [context]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L151-L157)
---
## streaming-error
### clj
_platforms: clj_
```clojure
(streaming-error provider message & {:keys [recoverable?]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L159-L164)
### cljs
_platforms: cljs_
```clojure
(streaming-error provider message & {:keys [recoverable?]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L159-L164)
---
## provider-error
### clj
_platforms: clj_
```clojure
(provider-error provider message & {:keys [http-status provider-code request-id recoverable?]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L166-L174)
### cljs
_platforms: cljs_
```clojure
(provider-error provider message & {:keys [http-status provider-code request-id recoverable?]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L166-L174)
---
## unsupported-reasoning-level
### clj
_platforms: clj_
```clojure
(unsupported-reasoning-level model-id requested-level)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L176-L184)
### cljs
_platforms: cljs_
```clojure
(unsupported-reasoning-level model-id requested-level)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L176-L184)
---
## tool-not-found
### clj
_platforms: clj_
```clojure
(tool-not-found tool-name available-tools)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L186-L195)
### cljs
_platforms: cljs_
```clojure
(tool-not-found tool-name available-tools)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L186-L195)
---
## validation-error
### clj
_platforms: clj_
```clojure
(validation-error tool-name args errors)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L197-L208)
### cljs
_platforms: cljs_
```clojure
(validation-error tool-name args errors)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L197-L208)
---
## http-status->error
### clj
_platforms: clj_
```clojure
(http-status->error status provider message & {:keys [provider-code retry-after request-id body]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L210-L230)
### cljs
_platforms: cljs_
```clojure
(http-status->error status provider message & {:keys [provider-code retry-after request-id body]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L210-L230)
---
## extract-retry-after
### clj
_platforms: clj_
```clojure
(extract-retry-after headers & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L243-L254)
### cljs
_platforms: cljs_
```clojure
(extract-retry-after headers & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L243-L254)
---
## extract-retry-after-from-message
### clj
_platforms: clj_
```clojure
(extract-retry-after-from-message message & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L256-L264)
### cljs
_platforms: cljs_
```clojure
(extract-retry-after-from-message message & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L256-L264)
---
## extract-retry-after-hint
### clj
_platforms: clj_
```clojure
(extract-retry-after-hint headers message & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L266-L269)
### cljs
_platforms: cljs_
```clojure
(extract-retry-after-hint headers message & {:keys [max-seconds] :or {max-seconds 60}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L266-L269)
---
## llx-error?
### clj
_platforms: clj_
```clojure
(llx-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L271-L274)
### cljs
_platforms: cljs_
```clojure
(llx-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L271-L274)
---
## recoverable?
### clj
_platforms: clj_
```clojure
(recoverable? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L276-L279)
### cljs
_platforms: cljs_
```clojure
(recoverable? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L276-L279)
---
## rate-limit-error?
### clj
_platforms: clj_
```clojure
(rate-limit-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L281-L284)
### cljs
_platforms: cljs_
```clojure
(rate-limit-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L281-L284)
---
## quota-exceeded-error?
### clj
_platforms: clj_
```clojure
(quota-exceeded-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L286-L289)
### cljs
_platforms: cljs_
```clojure
(quota-exceeded-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L286-L289)
---
## rate-limited-error?
### clj
_platforms: clj_
```clojure
(rate-limited-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L291-L294)
### cljs
_platforms: cljs_
```clojure
(rate-limited-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L291-L294)
---
## timeout-error?
### clj
_platforms: clj_
```clojure
(timeout-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L296-L299)
### cljs
_platforms: cljs_
```clojure
(timeout-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L296-L299)
---
## client-error?
### clj
_platforms: clj_
```clojure
(client-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L301-L304)
### cljs
_platforms: cljs_
```clojure
(client-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L301-L304)
---
## transient-error?
### clj
_platforms: clj_
```clojure
(transient-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L306-L309)
### cljs
_platforms: cljs_
```clojure
(transient-error? ex)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L306-L309)
---
## should-retry?
### clj
_platforms: clj_
```clojure
(should-retry? ex & {:keys [max-retries current-retry] :or {max-retries 2 current-retry 0}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L311-L315)
### cljs
_platforms: cljs_
```clojure
(should-retry? ex & {:keys [max-retries current-retry] :or {max-retries 2 current-retry 0}})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L311-L315)
---
## retry-delay-ms
### clj
_platforms: clj_
```clojure
(retry-delay-ms ex retry-count)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L317-L332)
### cljs
_platforms: cljs_
```clojure
(retry-delay-ms ex retry-count)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L317-L332)
---
## retry-loop-async
### clj
_platforms: clj_
```clojure
(retry-loop-async f max-retries sleep-fn)
(retry-loop-async f max-retries sleep-fn {:keys [call-id provider max-retry-delay-ms]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L334-L377)
### cljs
_platforms: cljs_
```clojure
(retry-loop-async f max-retries sleep-fn)
(retry-loop-async f max-retries sleep-fn {:keys [call-id provider max-retry-delay-ms]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/errors.cljc#L334-L377)
## ol.llx.ai.impl.model-catalog.generate
# ol.llx.ai.impl.model-catalog.generate
## supported-provider-rules
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L10-L14)
---
## default-generated-artifact-path
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L16-L16)
---
## default-overrides-path
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L17-L17)
---
## models-dev-url
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L18-L18)
---
## fetch-models-dev-data
```clojure
(fetch-models-dev-data)
```
Fetches and decodes the models.dev catalog JSON as a Clojure map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L20-L28)
---
## build-catalog
```clojure
(build-catalog {:keys [models-dev-data overrides]})
```
Builds canonical model entries from models.dev source data and local override patches.
`overrides` is a map of `model-id -> patch-map`.
Existing source models are patched in a fine-grained way (nested maps merged).
If a model id is absent in source, the patch map must include a full valid model definition.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L201-L221)
---
## render-generated-source
```clojure
(render-generated-source catalog)
```
Renders deterministic source for `ol.llx.ai.impl.models-generated` from normalized catalog entries.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L236-L248)
---
## write-generated-artifact!
```clojure
(write-generated-artifact! {:keys [path content]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L250-L253)
---
## generate-models!
```clojure
(generate-models! opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L259-L269)
---
## -main
```clojure
(-main & _args)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/model_catalog/generate.clj#L271-L274)
## ol.llx.ai.impl.models-generated
# ol.llx.ai.impl.models-generated
_platforms: clj, cljs_
## generated-models
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models_generated.cljc#L5-L1275)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models_generated.cljc#L5-L1275)
## ol.llx.ai.impl.models
# ol.llx.ai.impl.models
_platforms: clj, cljs_
## get-model
### clj
_platforms: clj_
```clojure
(get-model provider model-id)
```
Returns a model map for `provider` and `model-id`, or `nil` when not found.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L10-L13)
### cljs
_platforms: cljs_
```clojure
(get-model provider model-id)
```
Returns a model map for `provider` and `model-id`, or `nil` when not found.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L10-L13)
---
## get-providers
### clj
_platforms: clj_
```clojure
(get-providers)
```
Returns supported providers as a deterministic sorted vector.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L15-L20)
### cljs
_platforms: cljs_
```clojure
(get-providers)
```
Returns supported providers as a deterministic sorted vector.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L15-L20)
---
## get-models
### clj
_platforms: clj_
```clojure
(get-models provider)
```
Returns all models for `provider` as a deterministic vector sorted by `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L22-L27)
### cljs
_platforms: cljs_
```clojure
(get-models provider)
```
Returns all models for `provider` as a deterministic vector sorted by `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L22-L27)
---
## calculate-cost
### clj
_platforms: clj_
```clojure
(calculate-cost model usage)
```
Calculates usage cost totals for `model` rates and `usage` token counts.
Returns a map with `:input`, `:output`, `:cache-read`, `:cache-write`, and `:total`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L29-L51)
### cljs
_platforms: cljs_
```clojure
(calculate-cost model usage)
```
Calculates usage cost totals for `model` rates and `usage` token counts.
Returns a map with `:input`, `:output`, `:cache-read`, `:cache-write`, and `:total`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L29-L51)
---
## supports-xhigh?
### clj
_platforms: clj_
```clojure
(supports-xhigh? model)
```
Returns true when `model` supports xhigh reasoning effort.
Supports explicit capability flags and known model/API compatibility rules.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L53-L64)
### cljs
_platforms: cljs_
```clojure
(supports-xhigh? model)
```
Returns true when `model` supports xhigh reasoning effort.
Supports explicit capability flags and known model/API compatibility rules.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L53-L64)
---
## models-equal?
### clj
_platforms: clj_
```clojure
(models-equal? a b)
```
Returns true when both models are non-nil and share the same `:provider` and `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L66-L72)
### cljs
_platforms: cljs_
```clojure
(models-equal? a b)
```
Returns true when both models are non-nil and share the same `:provider` and `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/models.cljc#L66-L72)
## ol.llx.ai.impl.oauth.core
# ol.llx.ai.impl.oauth.core
_platforms: clj, cljs_
## refresh-oauth-token
### clj
_platforms: clj_
```clojure
(refresh-oauth-token provider-id credentials)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/core.cljc#L17-L24)
### cljs
_platforms: cljs_
```clojure
(refresh-oauth-token provider-id credentials)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/core.cljc#L17-L24)
---
## get-oauth-api-key
### clj
_platforms: clj_
```clojure
(get-oauth-api-key provider-id credentials-by-provider)
(get-oauth-api-key provider-id credentials-by-provider opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/core.cljc#L26-L56)
### cljs
_platforms: cljs_
```clojure
(get-oauth-api-key provider-id credentials-by-provider)
(get-oauth-api-key provider-id credentials-by-provider opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/core.cljc#L26-L56)
## ol.llx.ai.impl.oauth.openai-codex-jvm
# ol.llx.ai.impl.oauth.openai-codex-jvm
## client-id
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L16-L17)
---
## token-url
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L19-L20)
---
## create-authorization-flow
```clojure
(create-authorization-flow)
(create-authorization-flow {:keys [originator redirect-uri]})
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L84-L105)
---
## start-local-oauth-server
```clojure
(start-local-oauth-server expected-state)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L115-L164)
---
## exchange-authorization-code
```clojure
(exchange-authorization-code code verifier)
(exchange-authorization-code code verifier redirect-uri)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L204-L212)
---
## refresh-openai-codex-token
```clojure
(refresh-openai-codex-token refresh-token)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L214-L227)
---
## oauth-provider
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_jvm.clj#L229-L244)
## ol.llx.ai.impl.oauth.openai-codex-node
# ol.llx.ai.impl.oauth.openai-codex-node
## login-openai-codex
```clojure
(login-openai-codex _callbacks)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_node.cljs#L9-L11)
---
## refresh-openai-codex-token
```clojure
(refresh-openai-codex-token _refresh-token)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_node.cljs#L13-L15)
---
## oauth-provider
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex_node.cljs#L17-L25)
## ol.llx.ai.impl.oauth.openai-codex
# ol.llx.ai.impl.oauth.openai-codex
_platforms: clj, cljs_
## token-claim-path
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L6-L7)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L6-L7)
---
## default-redirect-uri
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L9-L10)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L9-L10)
---
## default-authorize-url
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L12-L13)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L12-L13)
---
## default-scope
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L15-L16)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L15-L16)
---
## parse-authorization-input
### clj
_platforms: clj_
```clojure
(parse-authorization-input input)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L50-L77)
### cljs
_platforms: cljs_
```clojure
(parse-authorization-input input)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L50-L77)
---
## account-id-from-access-token
### clj
_platforms: clj_
```clojure
(account-id-from-access-token token)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L99-L110)
### cljs
_platforms: cljs_
```clojure
(account-id-from-access-token token)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L99-L110)
---
## login-openai-codex
### clj
_platforms: clj_
```clojure
(login-openai-codex callbacks hooks)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L134-L177)
### cljs
_platforms: cljs_
```clojure
(login-openai-codex callbacks hooks)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/openai_codex.cljc#L134-L177)
## ol.llx.ai.impl.oauth.registry
# ol.llx.ai.impl.oauth.registry
_platforms: clj, cljs_
## register-oauth-provider!
### clj
_platforms: clj_
```clojure
(register-oauth-provider! provider)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L8-L14)
### cljs
_platforms: cljs_
```clojure
(register-oauth-provider! provider)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L8-L14)
---
## get-oauth-provider
### clj
_platforms: clj_
```clojure
(get-oauth-provider provider-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L16-L19)
### cljs
_platforms: cljs_
```clojure
(get-oauth-provider provider-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L16-L19)
---
## get-oauth-providers
### clj
_platforms: clj_
```clojure
(get-oauth-providers)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L21-L25)
### cljs
_platforms: cljs_
```clojure
(get-oauth-providers)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L21-L25)
---
## clear-oauth-providers!
### clj
_platforms: clj_
```clojure
(clear-oauth-providers!)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L27-L30)
### cljs
_platforms: cljs_
```clojure
(clear-oauth-providers!)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/oauth/registry.cljc#L27-L30)
## ol.llx.ai.impl.registry
# ol.llx.ai.impl.registry
_platforms: clj, cljs_
## adapters-key
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L6-L6)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L6-L6)
---
## tools-key
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L7-L7)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L7-L7)
---
## ->MutableRegistry
### clj
_platforms: clj_
```clojure
(->MutableRegistry registry*)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
### cljs
_platforms: cljs_
```clojure
(->MutableRegistry registry*)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
---
## MutableRegistry
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
---
## map->MutableRegistry
### clj
_platforms: clj_
```clojure
(map->MutableRegistry m)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
### cljs
_platforms: cljs_
```clojure
(map->MutableRegistry m)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L9-L9)
---
## ->DynamicRegistry
### clj
_platforms: clj_
```clojure
(->DynamicRegistry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
### cljs
_platforms: cljs_
```clojure
(->DynamicRegistry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
---
## DynamicRegistry
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
---
## map->DynamicRegistry
### clj
_platforms: clj_
```clojure
(map->DynamicRegistry m)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
### cljs
_platforms: cljs_
```clojure
(map->DynamicRegistry m)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L10-L10)
---
## *registry*
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L12-L12)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L12-L12)
---
## immutable-registry
### clj
_platforms: clj_
```clojure
(immutable-registry)
(immutable-registry registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L14-L19)
### cljs
_platforms: cljs_
```clojure
(immutable-registry)
(immutable-registry registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L14-L19)
---
## mutable-registry
### clj
_platforms: clj_
```clojure
(mutable-registry registry*)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L21-L23)
### cljs
_platforms: cljs_
```clojure
(mutable-registry registry*)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L21-L23)
---
## dynamic-registry
### clj
_platforms: clj_
```clojure
(dynamic-registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L25-L27)
### cljs
_platforms: cljs_
```clojure
(dynamic-registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L25-L27)
---
## resolve-registry
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L31-L31)
### clj
_platforms: clj_
```clojure
(resolve-registry)
(resolve-registry registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L41-L54)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L31-L31)
### cljs
_platforms: cljs_
```clojure
(resolve-registry)
(resolve-registry registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L41-L54)
---
## default-registry
### clj
_platforms: clj_
```clojure
(default-registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L33-L35)
### cljs
_platforms: cljs_
```clojure
(default-registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L33-L35)
---
## set-default-registry!
### clj
_platforms: clj_
```clojure
(set-default-registry! registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L37-L39)
### cljs
_platforms: cljs_
```clojure
(set-default-registry! registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L37-L39)
---
## register-adapter
### clj
_platforms: clj_
```clojure
(register-adapter registry adapter)
(register-adapter registry adapter source-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L56-L64)
### cljs
_platforms: cljs_
```clojure
(register-adapter registry adapter)
(register-adapter registry adapter source-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L56-L64)
---
## unregister-adapters-by-source
### clj
_platforms: clj_
```clojure
(unregister-adapters-by-source registry source-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L66-L77)
### cljs
_platforms: cljs_
```clojure
(unregister-adapters-by-source registry source-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L66-L77)
---
## clear-adapters
### clj
_platforms: clj_
```clojure
(clear-adapters registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L79-L81)
### cljs
_platforms: cljs_
```clojure
(clear-adapters registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L79-L81)
---
## get-adapter
### clj
_platforms: clj_
```clojure
(get-adapter api)
(get-adapter registry api)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L83-L90)
### cljs
_platforms: cljs_
```clojure
(get-adapter api)
(get-adapter registry api)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L83-L90)
---
## get-adapters
### clj
_platforms: clj_
```clojure
(get-adapters)
(get-adapters registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L92-L96)
### cljs
_platforms: cljs_
```clojure
(get-adapters)
(get-adapters registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/registry.cljc#L92-L96)
## ol.llx.ai.impl.schema
# ol.llx.ai.impl.schema
_platforms: clj, cljs_
## non-blank-string?
### clj
_platforms: clj_
```clojure
(non-blank-string? s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L11-L13)
### cljs
_platforms: cljs_
```clojure
(non-blank-string? s)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L11-L13)
---
## non-negative-int?
### clj
_platforms: clj_
```clojure
(non-negative-int? n)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L15-L17)
### cljs
_platforms: cljs_
```clojure
(non-negative-int? n)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L15-L17)
---
## non-negative-number?
### clj
_platforms: clj_
```clojure
(non-negative-number? n)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L19-L21)
### cljs
_platforms: cljs_
```clojure
(non-negative-number? n)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L19-L21)
---
## deferred?
### clj
_platforms: clj_
```clojure
(deferred? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L23-L25)
### cljs
_platforms: cljs_
```clojure
(deferred? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L23-L25)
---
## schema-form?
### clj
_platforms: clj_
```clojure
(schema-form? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L27-L33)
### cljs
_platforms: cljs_
```clojure
(schema-form? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L27-L33)
---
## schemas
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L35-L520)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L35-L520)
---
## custom-schemas
### clj
_platforms: clj_
```clojure
(custom-schemas)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L522-L525)
### cljs
_platforms: cljs_
```clojure
(custom-schemas)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L522-L525)
---
## registry
### clj
_platforms: clj_
```clojure
(registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L527-L530)
### cljs
_platforms: cljs_
```clojure
(registry)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L527-L530)
---
## schema
### clj
_platforms: clj_
```clojure
(schema schema-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L534-L536)
### cljs
_platforms: cljs_
```clojure
(schema schema-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L534-L536)
---
## valid?
### clj
_platforms: clj_
```clojure
(valid? schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L538-L540)
### cljs
_platforms: cljs_
```clojure
(valid? schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L538-L540)
---
## explain
### clj
_platforms: clj_
```clojure
(explain schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L542-L544)
### cljs
_platforms: cljs_
```clojure
(explain schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L542-L544)
---
## humanize
### clj
_platforms: clj_
```clojure
(humanize schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L546-L548)
### cljs
_platforms: cljs_
```clojure
(humanize schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L546-L548)
---
## assert-valid!
### clj
_platforms: clj_
```clojure
(assert-valid! schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L550-L558)
### cljs
_platforms: cljs_
```clojure
(assert-valid! schema-id data)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L550-L558)
---
## malli->json-schema
### clj
_platforms: clj_
```clojure
(malli->json-schema schema-form)
```
Converts a Malli schema form to a JSON Schema object map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L560-L565)
### cljs
_platforms: cljs_
```clojure
(malli->json-schema schema-form)
```
Converts a Malli schema form to a JSON Schema object map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/schema.cljc#L560-L565)
## ol.llx.ai.impl.transform-messages
# ol.llx.ai.impl.transform-messages
_platforms: clj, cljs_
## for-target-model
### clj
_platforms: clj_
```clojure
(for-target-model messages target-model opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/transform_messages.cljc#L145-L159)
### cljs
_platforms: cljs_
```clojure
(for-target-model messages target-model opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/transform_messages.cljc#L145-L159)
## ol.llx.ai.impl.utils.overflow
# ol.llx.ai.impl.utils.overflow
_platforms: clj, cljs_
## context-overflow?
### clj
_platforms: clj_
```clojure
(context-overflow? assistant-message)
(context-overflow? assistant-message context-window)
```
Returns true when `assistant-message` appears to be a context-window overflow.
Supports two detection modes:
1. error-message pattern matching for explicit overflow responses.
2. silent overflow heuristic when `context-window` is provided and
`(:usage :input + :cache-read)` exceeds it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/overflow.cljc#L32-L52)
### cljs
_platforms: cljs_
```clojure
(context-overflow? assistant-message)
(context-overflow? assistant-message context-window)
```
Returns true when `assistant-message` appears to be a context-window overflow.
Supports two detection modes:
1. error-message pattern matching for explicit overflow responses.
2. silent overflow heuristic when `context-window` is provided and
`(:usage :input + :cache-read)` exceeds it.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/overflow.cljc#L32-L52)
## ol.llx.ai.impl.utils.rate-limit
# ol.llx.ai.impl.utils.rate-limit
_platforms: clj, cljs_
## rate-limited?
### clj
_platforms: clj_
```clojure
(rate-limited? assistant-message)
```
Returns true when `assistant-message` appears to represent a provider
quota or rate-limit error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/rate_limit.cljc#L18-L27)
### cljs
_platforms: cljs_
```clojure
(rate-limited? assistant-message)
```
Returns true when `assistant-message` appears to represent a provider
quota or rate-limit error.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/rate_limit.cljc#L18-L27)
## ol.llx.ai.impl.utils.tool-validation
# ol.llx.ai.impl.utils.tool-validation
_platforms: clj, cljs_
## validate-tool-call
### clj
_platforms: clj_
```clojure
(validate-tool-call tools tool-call)
(validate-tool-call schema-registry tools tool-call)
```
Validates tool-call arguments against the matching tool `:input-schema`.
Returns coerced/validated arguments on success.
Throws `ExceptionInfo` with structured `ex-data`:
- `{:type :ol.llx/tool-not-found ...}` when `tool-call` name is unknown.
- `{:type :ol.llx/validation-error ...}` when arguments do not satisfy the schema.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/tool_validation.cljc#L17-L42)
### cljs
_platforms: cljs_
```clojure
(validate-tool-call tools tool-call)
(validate-tool-call schema-registry tools tool-call)
```
Validates tool-call arguments against the matching tool `:input-schema`.
Returns coerced/validated arguments on success.
Throws `ExceptionInfo` with structured `ex-data`:
- `{:type :ol.llx/tool-not-found ...}` when `tool-call` name is unknown.
- `{:type :ol.llx/validation-error ...}` when arguments do not satisfy the schema.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/tool_validation.cljc#L17-L42)
## ol.llx.ai.impl.utils.unicode
# ol.llx.ai.impl.utils.unicode
===
===
## sanitize-surrogates
===
```clojure
(sanitize-surrogates text)
```
Removes unpaired UTF-16 surrogate code units from `text`.
Preserves valid surrogate pairs (for example, emoji) and removes
standalone high/low surrogates that can break JSON serialization.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.clj#L5-L36)
===
```clojure
(sanitize-surrogates text)
```
Removes unpaired UTF-16 surrogate code units from `text`.
Preserves valid surrogate pairs and removes standalone high/low surrogates.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.cljs#L13-L38)
---
## sanitize-payload
===
```clojure
(sanitize-payload data)
```
Recursively walks a data structure, applying `sanitize-surrogates` to all strings.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.clj#L38-L41)
===
```clojure
(sanitize-payload data)
```
Recursively walks a data structure, applying `sanitize-surrogates` to all strings.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.cljs#L40-L43)
---
## truncate
===
```clojure
(truncate s max-len)
```
Returns `s` truncated to at most `max-len` characters.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.clj#L43-L49)
===
```clojure
(truncate s max-len)
```
Returns `s` truncated to at most `max-len` characters.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/impl/utils/unicode.cljs#L45-L51)
## ol.llx.ai.oauth.cli
# ol.llx.ai.oauth.cli
## -main
```clojure
(-main & args)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth/cli.clj#L79-L87)
## ol.llx.ai.oauth
# ol.llx.ai.oauth
_platforms: clj, cljs_
## register-oauth-provider!
### clj
_platforms: clj_
```clojure
(register-oauth-provider! provider)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L13-L15)
### cljs
_platforms: cljs_
```clojure
(register-oauth-provider! provider)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L13-L15)
---
## get-oauth-provider
### clj
_platforms: clj_
```clojure
(get-oauth-provider provider-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L17-L19)
### cljs
_platforms: cljs_
```clojure
(get-oauth-provider provider-id)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L17-L19)
---
## get-oauth-providers
### clj
_platforms: clj_
```clojure
(get-oauth-providers)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L21-L23)
### cljs
_platforms: cljs_
```clojure
(get-oauth-providers)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L21-L23)
---
## refresh-oauth-token
### clj
_platforms: clj_
```clojure
(refresh-oauth-token provider-id credentials)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L25-L27)
### cljs
_platforms: cljs_
```clojure
(refresh-oauth-token provider-id credentials)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L25-L27)
---
## get-oauth-api-key
### clj
_platforms: clj_
```clojure
(get-oauth-api-key provider-id credentials-by-provider)
(get-oauth-api-key provider-id credentials-by-provider opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L29-L33)
### cljs
_platforms: cljs_
```clojure
(get-oauth-api-key provider-id credentials-by-provider)
(get-oauth-api-key provider-id credentials-by-provider opts)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/oauth.cljc#L29-L33)
## ol.llx.ai.registry
# ol.llx.ai.registry
_platforms: clj, cljs_
## immutable-registry
### clj
_platforms: clj_
```clojure
(immutable-registry)
(immutable-registry registry-map)
```
Creates an immutable registry map.
With no argument it creates an empty normalized registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L5-L12)
### cljs
_platforms: cljs_
```clojure
(immutable-registry)
(immutable-registry registry-map)
```
Creates an immutable registry map.
With no argument it creates an empty normalized registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L5-L12)
---
## mutable-registry
### clj
_platforms: clj_
```clojure
(mutable-registry registry*)
```
Wraps an atom-backed registry for mutable use cases.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L14-L17)
### cljs
_platforms: cljs_
```clojure
(mutable-registry registry*)
```
Wraps an atom-backed registry for mutable use cases.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L14-L17)
---
## dynamic-registry
### clj
_platforms: clj_
```clojure
(dynamic-registry)
```
Creates a dynamic registry wrapper that resolves from thread-local bindings.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L19-L22)
### cljs
_platforms: cljs_
```clojure
(dynamic-registry)
```
Creates a dynamic registry wrapper that resolves from thread-local bindings.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L19-L22)
---
## default-registry
### clj
_platforms: clj_
```clojure
(default-registry)
```
Returns the process default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L24-L27)
### cljs
_platforms: cljs_
```clojure
(default-registry)
```
Returns the process default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L24-L27)
---
## set-default-registry!
### clj
_platforms: clj_
```clojure
(set-default-registry! registry-value)
```
Sets the process default registry after resolving wrapper implementations.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L29-L32)
### cljs
_platforms: cljs_
```clojure
(set-default-registry! registry-value)
```
Sets the process default registry after resolving wrapper implementations.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L29-L32)
---
## resolve-registry
### clj
_platforms: clj_
```clojure
(resolve-registry)
(resolve-registry registry-value)
```
Resolves supported registry wrapper implementations to a registry map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L34-L39)
### cljs
_platforms: cljs_
```clojure
(resolve-registry)
(resolve-registry registry-value)
```
Resolves supported registry wrapper implementations to a registry map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L34-L39)
---
## register-adapter
### clj
_platforms: clj_
```clojure
(register-adapter registry-value adapter)
(register-adapter registry-value adapter source-id)
```
Registers `adapter` in `registry-value`, with optional `source-id` metadata.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L41-L46)
### cljs
_platforms: cljs_
```clojure
(register-adapter registry-value adapter)
(register-adapter registry-value adapter source-id)
```
Registers `adapter` in `registry-value`, with optional `source-id` metadata.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L41-L46)
---
## unregister-adapters-by-source
### clj
_platforms: clj_
```clojure
(unregister-adapters-by-source registry-value source-id)
```
Removes adapters from `registry-value` whose registration `source-id` matches.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L48-L51)
### cljs
_platforms: cljs_
```clojure
(unregister-adapters-by-source registry-value source-id)
```
Removes adapters from `registry-value` whose registration `source-id` matches.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L48-L51)
---
## clear-adapters
### clj
_platforms: clj_
```clojure
(clear-adapters registry-value)
```
Removes all adapters from `registry-value`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L53-L56)
### cljs
_platforms: cljs_
```clojure
(clear-adapters registry-value)
```
Removes all adapters from `registry-value`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L53-L56)
---
## get-adapter
### clj
_platforms: clj_
```clojure
(get-adapter api)
(get-adapter registry-value api)
```
Returns the adapter for an API key.
With one argument, uses the default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L58-L65)
### cljs
_platforms: cljs_
```clojure
(get-adapter api)
(get-adapter registry-value api)
```
Returns the adapter for an API key.
With one argument, uses the default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L58-L65)
---
## get-adapters
### clj
_platforms: clj_
```clojure
(get-adapters)
(get-adapters registry-value)
```
Returns all adapters in the registry.
With no argument, uses the default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L67-L74)
### cljs
_platforms: cljs_
```clojure
(get-adapters)
(get-adapters registry-value)
```
Returns all adapters in the registry.
With no argument, uses the default registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/registry.cljc#L67-L74)
## ol.llx.ai.schema.runtime
# ol.llx.ai.schema.runtime
_platforms: clj, cljs_
## stream-channel?
### clj
_platforms: clj_
```clojure
(stream-channel? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/schema/runtime.cljc#L5-L7)
### cljs
_platforms: cljs_
```clojure
(stream-channel? x)
```
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/schema/runtime.cljc#L5-L7)
---
## schemas
### clj
_platforms: clj_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/schema/runtime.cljc#L9-L148)
### cljs
_platforms: cljs_
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai/schema/runtime.cljc#L9-L148)
## ol.llx.ai
# ol.llx.ai
_platforms: clj, cljs_
## default-env
### clj
_platforms: clj_
```clojure
(default-env)
```
Returns the default environment for your platform.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L12-L17)
### cljs
_platforms: cljs_
```clojure
(default-env)
```
Returns the default environment for your platform.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L12-L17)
---
## complete*
### clj
_platforms: clj_
```clojure
(complete* env model context opts)
```
Runs one non-streaming assistant turn using provider-style options.
Use `complete*` when you need provider-level control and adapter-specific
options. This is the provider path.
`opts` is validated against `:ol.llx/provider-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Adapter `build-request` boundaries also validate adapter-specific schemas:
`:ol.llx/openai-completions-provider-options`,
`:ol.llx/openai-responses-provider-options`,
`:ol.llx/anthropic-provider-options`, and
`:ol.llx/google-provider-options`.
Common provider option keys:
| | |
| --- | --- |
| key | description |
| `:max-output-tokens` | Requested output token cap. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Provider reasoning map (shape depends on adapter). |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
| `:max-retries` | Retry count for transient failures (default `2`). |
Returns a promise that resolves to one canonical assistant message map.
Errors reject the promise with structured LLX exception data.
Use [`complete`](#complete) for the unified API.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L19-L56)
### cljs
_platforms: cljs_
```clojure
(complete* env model context opts)
```
Runs one non-streaming assistant turn using provider-style options.
Use `complete*` when you need provider-level control and adapter-specific
options. This is the provider path.
`opts` is validated against `:ol.llx/provider-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Adapter `build-request` boundaries also validate adapter-specific schemas:
`:ol.llx/openai-completions-provider-options`,
`:ol.llx/openai-responses-provider-options`,
`:ol.llx/anthropic-provider-options`, and
`:ol.llx/google-provider-options`.
Common provider option keys:
| | |
| --- | --- |
| key | description |
| `:max-output-tokens` | Requested output token cap. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Provider reasoning map (shape depends on adapter). |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
| `:max-retries` | Retry count for transient failures (default `2`). |
Returns a promise that resolves to one canonical assistant message map.
Errors reject the promise with structured LLX exception data.
Use [`complete`](#complete) for the unified API.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L19-L56)
---
## stream*
### clj
_platforms: clj_
```clojure
(stream* env model context opts)
```
Runs one streaming assistant turn using provider-style options.
Use `stream*` when you need provider-level control and adapter-specific
options. This is the provider path.
`opts` is validated against `:ol.llx/provider-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`, and adapter-specific schemas are
enforced at each adapter `build-request` boundary.
Common provider option keys:
| | |
| --- | --- |
| key | description |
| `:max-output-tokens` | Requested output token cap. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Provider reasoning map (shape depends on adapter). |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
| `:max-retries` | Retry count for transient failures (default `2`). |
Returns a Promesa CSP channel emitting canonical LLX event maps.
Stream completion emits terminal `:done` or `:error` and closes the channel.
Consumer cancellation is channel closure.
Use [`stream`](#stream) for the unified API.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L58-L91)
### cljs
_platforms: cljs_
```clojure
(stream* env model context opts)
```
Runs one streaming assistant turn using provider-style options.
Use `stream*` when you need provider-level control and adapter-specific
options. This is the provider path.
`opts` is validated against `:ol.llx/provider-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`, and adapter-specific schemas are
enforced at each adapter `build-request` boundary.
Common provider option keys:
| | |
| --- | --- |
| key | description |
| `:max-output-tokens` | Requested output token cap. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Provider reasoning map (shape depends on adapter). |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
| `:max-retries` | Retry count for transient failures (default `2`). |
Returns a Promesa CSP channel emitting canonical LLX event maps.
Stream completion emits terminal `:done` or `:error` and closes the channel.
Consumer cancellation is channel closure.
Use [`stream`](#stream) for the unified API.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L58-L91)
---
## complete
### clj
_platforms: clj_
```clojure
(complete env model context unified-opts)
```
Runs one non-streaming assistant turn using the unified options API.
This is the unified path and is recommended for most callers.
`opts` is validated against `:ol.llx/unified-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Unified options are normalized and forwarded to provider adapters.
For example:
- `:max-tokens` -> provider `:max-output-tokens`
- `:reasoning` / `:reasoning-effort` -> provider reasoning shape
Unified option keys:
| | |
| --- | --- |
| key | description |
| `:max-tokens` | Requested output cap. Defaults to `min(model.max-tokens, 32000)`. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Reasoning level keyword (`:minimal` `:low` `:medium` `:high` `:xhigh`). |
| `:reasoning-effort` | Alias for `:reasoning`. |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:thinking-budgets` | Optional token budgets per reasoning level (provider-dependent). |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
Returns a promise that resolves to one canonical assistant message map.
Errors reject the promise with structured LLX exception data.
Use [`complete*`](#complete*) if you need provider-specific options that are outside the
unified schema.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L93-L129)
### cljs
_platforms: cljs_
```clojure
(complete env model context unified-opts)
```
Runs one non-streaming assistant turn using the unified options API.
This is the unified path and is recommended for most callers.
`opts` is validated against `:ol.llx/unified-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Unified options are normalized and forwarded to provider adapters.
For example:
- `:max-tokens` -> provider `:max-output-tokens`
- `:reasoning` / `:reasoning-effort` -> provider reasoning shape
Unified option keys:
| | |
| --- | --- |
| key | description |
| `:max-tokens` | Requested output cap. Defaults to `min(model.max-tokens, 32000)`. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Reasoning level keyword (`:minimal` `:low` `:medium` `:high` `:xhigh`). |
| `:reasoning-effort` | Alias for `:reasoning`. |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:thinking-budgets` | Optional token budgets per reasoning level (provider-dependent). |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
Returns a promise that resolves to one canonical assistant message map.
Errors reject the promise with structured LLX exception data.
Use [`complete*`](#complete*) if you need provider-specific options that are outside the
unified schema.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L93-L129)
---
## stream
### clj
_platforms: clj_
```clojure
(stream env model context unified-opts)
```
Runs one streaming assistant turn using the unified options API.
This is the unified path and is recommended for most callers.
`opts` is validated against `:ol.llx/unified-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Unified option keys:
| | |
| --- | --- |
| key | description |
| `:max-tokens` | Requested output cap. Defaults to `min(model.max-tokens, 32000)`. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Reasoning level keyword (`:minimal` `:low` `:medium` `:high` `:xhigh`). |
| `:reasoning-effort` | Alias for `:reasoning`. |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:thinking-budgets` | Optional token budgets per reasoning level (provider-dependent). |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
Returns a Promesa CSP channel emitting canonical LLX event maps.
Stream completion emits terminal `:done` or `:error` and closes the channel.
Consumer cancellation is channel closure.
Use [`stream*`](#stream*) for provider-specific option control.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L131-L162)
### cljs
_platforms: cljs_
```clojure
(stream env model context unified-opts)
```
Runs one streaming assistant turn using the unified options API.
This is the unified path and is recommended for most callers.
`opts` is validated against `:ol.llx/unified-request-options` in
`src/ol/llx/ai/impl/schema/options.cljc`.
Unified option keys:
| | |
| --- | --- |
| key | description |
| `:max-tokens` | Requested output cap. Defaults to `min(model.max-tokens, 32000)`. |
| `:temperature` | Sampling temperature. |
| `:top-p` | Nucleus sampling probability. |
| `:reasoning` | Reasoning level keyword (`:minimal` `:low` `:medium` `:high` `:xhigh`). |
| `:reasoning-effort` | Alias for `:reasoning`. |
| `:session-id` | Optional session identifier for provider-side caching/routing. |
| `:thinking-budgets` | Optional token budgets per reasoning level (provider-dependent). |
| `:api-key` | Provider API key override for this call. |
| `:headers` | Additional provider request headers. |
| `:signal` | Abort/cancel signal forwarded to runtime/provider layer. |
| `:max-retry-delay-ms` | Optional cap for server-requested retry delays. |
| `:metadata` | Request metadata map forwarded to adapter payload builders. |
| `:registry` | Per-call adapter registry override. |
Returns a Promesa CSP channel emitting canonical LLX event maps.
Stream completion emits terminal `:done` or `:error` and closes the channel.
Consumer cancellation is channel closure.
Use [`stream*`](#stream*) for provider-specific option control.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L131-L162)
---
## get-model
### clj
_platforms: clj_
```clojure
(get-model provider model-id)
```
Returns the model definition for `provider` and `model-id`, or `nil` when absent.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L164-L167)
### cljs
_platforms: cljs_
```clojure
(get-model provider model-id)
```
Returns the model definition for `provider` and `model-id`, or `nil` when absent.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L164-L167)
---
## get-models
### clj
_platforms: clj_
```clojure
(get-models provider)
```
Returns all models for `provider` as a deterministic vector sorted by `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L169-L172)
### cljs
_platforms: cljs_
```clojure
(get-models provider)
```
Returns all models for `provider` as a deterministic vector sorted by `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L169-L172)
---
## get-providers
### clj
_platforms: clj_
```clojure
(get-providers)
```
Returns supported providers as a deterministic sorted vector.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L174-L177)
### cljs
_platforms: cljs_
```clojure
(get-providers)
```
Returns supported providers as a deterministic sorted vector.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L174-L177)
---
## calculate-cost
### clj
_platforms: clj_
```clojure
(calculate-cost model usage)
```
Calculates usage cost totals from `model` pricing and `usage` token counts.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L179-L182)
### cljs
_platforms: cljs_
```clojure
(calculate-cost model usage)
```
Calculates usage cost totals from `model` pricing and `usage` token counts.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L179-L182)
---
## supports-xhigh?
### clj
_platforms: clj_
```clojure
(supports-xhigh? model)
```
Returns true when `model` supports `:xhigh` reasoning effort.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L184-L187)
### cljs
_platforms: cljs_
```clojure
(supports-xhigh? model)
```
Returns true when `model` supports `:xhigh` reasoning effort.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L184-L187)
---
## models-equal?
### clj
_platforms: clj_
```clojure
(models-equal? a b)
```
Returns true when two model maps refer to the same `:provider` and `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L189-L192)
### cljs
_platforms: cljs_
```clojure
(models-equal? a b)
```
Returns true when two model maps refer to the same `:provider` and `:id`.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L189-L192)
---
## validate-tool-call
### clj
_platforms: clj_
```clojure
(validate-tool-call tools tool-call)
(validate-tool-call schema-registry tools tool-call)
```
Validates a `tool-call` entry against matching tool definitions in `tools`.
* `(validate-tool-call tools tool-call)` uses the default LLX registry.
* `(validate-tool-call schema-registry tools tool-call)` uses an explicit
active registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L194-L203)
### cljs
_platforms: cljs_
```clojure
(validate-tool-call tools tool-call)
(validate-tool-call schema-registry tools tool-call)
```
Validates a `tool-call` entry against matching tool definitions in `tools`.
* `(validate-tool-call tools tool-call)` uses the default LLX registry.
* `(validate-tool-call schema-registry tools tool-call)` uses an explicit
active registry.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L194-L203)
---
## context-overflow?
### clj
_platforms: clj_
```clojure
(context-overflow? assistant-message)
(context-overflow? assistant-message context-window)
```
Returns true when the input matches known context-window overflow patterns.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L205-L210)
### cljs
_platforms: cljs_
```clojure
(context-overflow? assistant-message)
(context-overflow? assistant-message context-window)
```
Returns true when the input matches known context-window overflow patterns.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L205-L210)
---
## sanitize-surrogates
### clj
_platforms: clj_
```clojure
(sanitize-surrogates text)
```
Removes unpaired surrogate code units while preserving valid Unicode pairs.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L212-L215)
### cljs
_platforms: cljs_
```clojure
(sanitize-surrogates text)
```
Removes unpaired surrogate code units while preserving valid Unicode pairs.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L212-L215)
---
## schema-regsitry
### clj
_platforms: clj_
```clojure
(schema-regsitry)
```
Returns the full LLX schema registry map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L217-L220)
### cljs
_platforms: cljs_
```clojure
(schema-regsitry)
```
Returns the full LLX schema registry map.
[source,window=_blank](https://github.com/outskirtslabs/llx/blob/main/src/ol/llx/ai.cljc#L217-L220)
## ol.llx
# ol.llx
> Unified LLM API and agent runtime for Clojure, ClojureScript, and Clojure Dart.



## ol.llx.ai
`ol.llx.ai` targets JVM Clojure, ClojureScript, and ClojureDart. Under the hood it is built around the API families that cover most of the current LLM ecosystem: OpenAI Completions, OpenAI Responses, Anthropic Messages, and Google Generative AI.
It absorbs provider quirks around streaming, tool calls, reasoning traces, and model behavior to present a unified API without relying on vendor SDKs or Java wrappers.
## ol.llx.agent
`ol.llx.agent` sits on top of `ol.llx.ai` and handles the multi-turn agent loop.
Feature highlights:
* tools run concurrently and can be aborted mid-execution
* the steering queue can interrupt an active run, skip pending tools, and re-prompt
* the follow-up queue can defer messages until the current turn finishes
* event subscriptions expose structured turn, message, and tool lifecycle events
* agent state can be snapshotted and rehydrated by the application
* custom message roles allow app-specific history that is filtered before LLM submission
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Installation
Only git dep usage will be supported for now while we are in the experimental phase.
```clojure
;; deps.edn
{:deps {com.outskirtslabs/llx
{:git/url "https://github.com/outskirtslabs/llx.git"
:git/sha "ea558cd4fbc9518cd7a23bddc380c303fed549d1"}}}
```
## Quick Start
```clojure
(require '[ol.llx.ai :as ai]
'[ol.llx.agent :as agent]
'[promesa.exec.csp :as sp])
(def read-tool
{:name "read_file"
:description "Read the contents of a file at the given path."
:input-schema [:map [:path :string]]
:execute (fn [_id {:keys [path]} _abort _on-update]
{:content [{:type :text :text (slurp path)}]})})
(def a (agent/create-agent {:model (ai/get-model :anthropic "claude-sonnet-4-6")
:system-prompt "You are a helpful assistant."
:tools [read-tool]}))
;; subscribe to events
(let [ch (agent/subscribe a)]
(future
(loop []
(when-let [ev (sp/take! ch)]
(println (:type ev))
(recur)))))
(agent/prompt a [{:role :user :content "read README.adoc" :timestamp 1}])
(agent/close a)
```
`ol.llx.ai` exposes unified entrypoints at `ol.llx.ai/complete` and `ol.llx.ai/stream`, with `complete*` and `stream*` available when you need provider-specific escape hatches.
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.llx/next/)
* [API Reference](https://docs.outskirtslabs.com/ol.llx/next/api)
* [Example: Minimal Coding Agent](examples/minimal-coding-agent/README.md)
* [Support via GitHub Issues](https://github.com/outskirtslabs/llx/issues)
## License
Copyright (C) 2026 Casey Link
Distributed under the [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
## ol.sfv.impl
# ol.sfv.impl
## token
```clojure
(token s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L15-L15)
---
## token?
```clojure
(token? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L16-L16)
---
## decimal
```clojure
(decimal x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L18-L18)
---
## decimal?
```clojure
(decimal? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L19-L19)
---
## integer
```clojure
(integer n)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L21-L21)
---
## integer?
```clojure
(integer? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L22-L22)
---
## string
```clojure
(string s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L24-L24)
---
## string?
```clojure
(string? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L25-L25)
---
## dstring
```clojure
(dstring s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L27-L27)
---
## dstring?
```clojure
(dstring? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L28-L28)
---
## bytes
```clojure
(bytes b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L30-L30)
---
## bytes?
```clojure
(bytes? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L31-L31)
---
## bool
```clojure
(bool b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L33-L33)
---
## bool?
```clojure
(bool? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L34-L34)
---
## date
```clojure
(date secs)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L36-L36)
---
## date?
```clojure
(date? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L37-L37)
---
## params
```clojure
(params & kvs)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L40-L47)
---
## param-get
```clojure
(param-get ps k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L49-L49)
---
## param-keys
```clojure
(param-keys ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L50-L50)
---
## item
```clojure
(item bare)
(item bare ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L53-L55)
---
## item?
```clojure
(item? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L56-L56)
---
## item-bare
```clojure
(item-bare i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L58-L58)
---
## item-params
```clojure
(item-params i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L59-L59)
---
## inner-list
```clojure
(inner-list items)
(inner-list items ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L61-L63)
---
## inner-list?
```clojure
(inner-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L65-L65)
---
## inner-items
```clojure
(inner-items il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L66-L66)
---
## inner-params
```clojure
(inner-params il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L67-L67)
---
## sf-list
```clojure
(sf-list members)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L69-L69)
---
## sf-list?
```clojure
(sf-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L71-L71)
---
## list-members
```clojure
(list-members l)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L72-L72)
---
## sf-dict
```clojure
(sf-dict entries)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L73-L73)
---
## sf-dict?
```clojure
(sf-dict? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L75-L75)
---
## dict-keys
```clojure
(dict-keys d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L76-L76)
---
## dict-get
```clojure
(dict-get d k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L77-L77)
---
## dict->pairs
```clojure
(dict->pairs d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L78-L78)
---
## flag
```clojure
(flag)
(flag ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L80-L82)
---
## flag?
```clojure
(flag? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L84-L84)
---
## ascii-string
```clojure
(ascii-string s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L89-L98)
---
## validate-field-line
```clojure
(validate-field-line s)
```
Validate field line characters and return position of first invalid character, or nil if valid
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L100-L109)
---
## init-ctx
```clojure
(init-ctx s-or-bytes)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L111-L113)
---
## eof?
```clojure
(eof? ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L115-L115)
---
## peek-char
```clojure
(peek-char ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L116-L118)
---
## consume-char
```clojure
(consume-char ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L120-L121)
---
## skip-ows
```clojure
(skip-ows ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L123-L126)
---
## skip-sp
```clojure
(skip-sp ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L128-L131)
---
## digit?
```clojure
(digit? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L136-L137)
---
## alpha?
```clojure
(alpha? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L139-L141)
---
## lcalpha?
```clojure
(lcalpha? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L143-L144)
---
## tchar?
```clojure
(tchar? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L148-L148)
---
## token-char?
```clojure
(token-char? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L149-L149)
---
## lc-hexdig?
```clojure
(lc-hexdig? ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L152-L154)
---
## hex-digit-value
```clojure
(hex-digit-value ch)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L156-L160)
---
## parse-error
```clojure
(parse-error ctx reason & {:keys [found expected]})
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L162-L165)
---
## parse-key
```clojure
(parse-key ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L170-L187)
---
## parse-integer-or-decimal
```clojure
(parse-integer-or-decimal ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L193-L246)
---
## parse-string
```clojure
(parse-string ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L248-L277)
---
## parse-token
```clojure
(parse-token ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L278-L300)
---
## parse-byte-sequence
```clojure
(parse-byte-sequence ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L301-L332)
---
## parse-sfv-boolean
```clojure
(parse-sfv-boolean ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L334-L354)
---
## parse-date
```clojure
(parse-date ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L356-L374)
---
## decode-percent-sequence
```clojure
(decode-percent-sequence ctx)
```
Decode a percent-encoded sequence (%XX) into a byte value.
Returns [byte new-ctx] or throws parse error.
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L376-L389)
---
## parse-display-string
```clojure
(parse-display-string ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L391-L498)
---
## parse-bare-item
```clojure
(parse-bare-item ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L500-L534)
---
## parse-parameters
```clojure
(parse-parameters ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L536-L568)
---
## parse-item
```clojure
(parse-item ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L569-L576)
---
## parse-inner-list
```clojure
(parse-inner-list ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L578-L602)
---
## parse-item-or-inner-list
```clojure
(parse-item-or-inner-list ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L604-L611)
---
## parse-list-members
```clojure
(parse-list-members ctx)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L613-L632)
---
## parse-list
```clojure
(parse-list s-or-bytes)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L634-L642)
---
## parse-dict
```clojure
(parse-dict s-or-bytes)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L644-L693)
---
## parse
```clojure
(parse field-type s-or-bytes)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L695-L720)
---
## serialize-integer
```clojure
(serialize-integer v)
```
RFC 9651 §4.1.4: Serializing an Integer
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L725-L731)
---
## serialize-decimal
```clojure
(serialize-decimal v)
```
RFC 9651 §4.1.5: Serializing a Decimal
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L733-L754)
---
## serialize-string
```clojure
(serialize-string v)
```
RFC 9651 §4.1.6: Serializing a String
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L756-L769)
---
## serialize-token
```clojure
(serialize-token v)
```
RFC 9651 §4.1.7: Serializing a Token
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L771-L783)
---
## serialize-byte-sequence
```clojure
(serialize-byte-sequence v)
```
RFC 9651 §4.1.8: Serializing a Byte Sequence
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L785-L790)
---
## serialize-boolean
```clojure
(serialize-boolean v)
```
RFC 9651 §4.1.9: Serializing a Boolean
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L792-L795)
---
## serialize-date
```clojure
(serialize-date v)
```
RFC 9651 §4.1.10: Serializing a Date
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L797-L803)
---
## serialize-display-string
```clojure
(serialize-display-string v)
```
RFC 9651 §4.1.11: Serializing a Display String
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L805-L820)
---
## serialize-bare-item
```clojure
(serialize-bare-item item)
```
RFC 9651 §4.1.3.1: Serializing a Bare Item
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L822-L835)
---
## serialize-key
```clojure
(serialize-key k)
```
RFC 9651 §4.1.1.3: Serializing a Key
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L837-L850)
---
## serialize-parameters
```clojure
(serialize-parameters params)
```
RFC 9651 §4.1.1.2: Serializing Parameters
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L852-L863)
---
## serialize-item
```clojure
(serialize-item item)
```
RFC 9651 §4.1.3: Serializing an Item
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L865-L871)
---
## serialize-inner-list
```clojure
(serialize-inner-list inner-list)
```
RFC 9651 §4.1.1.1: Serializing an Inner List
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L873-L888)
---
## serialize-list
```clojure
(serialize-list lst)
```
RFC 9651 §4.1.1: Serializing a List
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L890-L906)
---
## serialize-dict
```clojure
(serialize-dict dict)
```
RFC 9651 §4.1.2: Serializing a Dictionary
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L908-L934)
---
## serialize
```clojure
(serialize x)
```
RFC 9651 §4.1: Serializing Structured Fields
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L936-L943)
---
## combine-field-lines
```clojure
(combine-field-lines lines)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv/impl.clj#L945-L945)
## ol.sfv
# ol.sfv
RFC 9651 Structured Field Values for HTTP.
See the project website for info on motivation and design:
<https://github.com/outskirtslabs/sfv>
This ns provides parsing and serialization of Structured Fields containing
Items, Lists, and Dictionaries with Parameters. Returns precise AST
representations that round-trip byte-for-byte with RFC 9651 strings.
Primary functions: [`parse`](#parse), [`parse-item`](#parse-item), [`parse-list`](#parse-list), [`parse-dict`](#parse-dict),
[`serialize`](#serialize)
## Conventions
* `s-or-bytes`: Input string or byte array (decoded as ascii) containing HTTP field value
* `field-type`: One of `:item`, `:list`, or `:dict` specifying the top-level structure
* `:bare`: The raw value within an Item (Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String)
## Primitive Types
Each Item’s `:bare` is one of these RFC 9651 types:
| | | | |
| --- | --- | --- | --- |
| SFV type | Header | AST example | Clojure type (`:value`) |
| Integer | `42`, `-17`, `999999999999999` | `{:type :integer :value 1618884473}` | `long` |
| Decimal | `3.14`, `-0.5` | `{:type :decimal :value 3.14M}` | `BigDecimal` |
| String | `" hello world "` | `{:type :string :value " hello "}` | `java.lang.String` |
| Token | `simple-token` | `{:type :token :value " simple-token "}` | `String` |
| Byte Sequence | `:SGVsbG8=:` | `{:type :bytes :value }` | `byte[]` |
| Boolean | `?1` / `?0` | `{:type :boolean :value true}` | `true` / `false` |
| Date | `@1659578233` | `{:type :date :value 1659578233}` | epoch seconds as `long` |
| Display String | `%" Gr%c3%bc%c3%9fe "` | `{:type :display :value " Grüße "}` | `String` (percent-decoded, validated) |
## parse
```clojure
(parse field-type s-or-bytes)
```
Parse a Structured Field of the given `field-type`.
Takes a `field-type` (`:list`, `:dict`, or `:item`) and a string or byte array.
Returns an AST representation following RFC 9651.
```clojure
(parse :item "42")
;; => {:type :item :bare {:type :integer :value 42} :params []}
(parse :dict "max-age=3600, must-revalidate")
;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L39-L53)
---
## parse-list
```clojure
(parse-list s-or-bytes)
```
Parse a Structured Field List from `s-or-bytes`.
Returns a List AST with `:type :list` and `:members` vector containing Items and Inner Lists.
```clojure
(parse-list "apple, pear;sweet=true, orange")
;; => {:type :list :members [...]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L55-L65)
---
## parse-dict
```clojure
(parse-dict s-or-bytes)
```
Parse a Structured Field Dictionary from `s-or-bytes`.
Returns a Dictionary AST with `:type :dict` and `:entries` as ordered key-value pairs.
Each entry maps from a key to either an Item or Inner List.
```clojure
(parse-dict "max-age=3600, must-revalidate")
;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L67-L78)
---
## parse-item
```clojure
(parse-item s-or-bytes)
```
Parse a Structured Field Item from `s-or-bytes`.
Returns an Item AST with `:type :item`, `:bare` value, and `:params` Parameters.
The bare value can be Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String.
```clojure
(parse-item "42")
;; => {:type :item :bare {:type :integer :value 42} :params []}
(parse-item "pear;sweet")
;; => {:type :item :bare {:type :token :value "pear"} :params `"sweet" {...}`}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L80-L94)
---
## serialize
```clojure
(serialize x)
```
Serialize a Structured Field AST `x` to its string representation.
Takes any parsed AST (Item, List, or Dictionary) and returns the RFC 9651 string.
See [`serialize-item`](api/ol-sfv-impl.adoc#serialize-item), [`serialize-list`](api/ol-sfv-impl.adoc#serialize-list), and [`serialize-dict`](api/ol-sfv-impl.adoc#serialize-dict) for type-specific variants.
```clojure
(serialize {:type :item :bare {:type :integer :value 42} :params []})
;; => "42"
(serialize-list {:type :list :members [...]})
;; => "apple, pear;sweet=true, orange"
(serialize-dict {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]})
;; => "max-age=3600, must-revalidate"
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L96-L113)
---
## token
```clojure
(token s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L116-L117)
---
## token?
```clojure
(token? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L119-L120)
---
## decimal
```clojure
(decimal x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L122-L123)
---
## decimal?
```clojure
(decimal? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L124-L125)
---
## integer
```clojure
(integer n)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L127-L128)
---
## integer?
```clojure
(integer? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L130-L131)
---
## string
```clojure
(string s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L133-L134)
---
## string?
```clojure
(string? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L136-L137)
---
## dstring
```clojure
(dstring s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L139-L140)
---
## dstring?
```clojure
(dstring? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L142-L143)
---
## bytes
```clojure
(bytes b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L145-L146)
---
## bytes?
```clojure
(bytes? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L148-L149)
---
## bool
```clojure
(bool b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L151-L152)
---
## bool?
```clojure
(bool? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L154-L155)
---
## date
```clojure
(date seconds)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L157-L158)
---
## date?
```clojure
(date? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L160-L161)
---
## params
```clojure
(params & kvs)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L164-L165)
---
## param-get
```clojure
(param-get ps k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L167-L168)
---
## param-keys
```clojure
(param-keys ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L170-L171)
---
## item
```clojure
(item bare)
(item bare ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L174-L178)
---
## item?
```clojure
(item? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L180-L181)
---
## item-bare
```clojure
(item-bare i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L183-L184)
---
## item-params
```clojure
(item-params i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L186-L187)
---
## inner-list
```clojure
(inner-list items)
(inner-list items ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L190-L194)
---
## inner-list?
```clojure
(inner-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L196-L197)
---
## inner-items
```clojure
(inner-items il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L199-L200)
---
## inner-params
```clojure
(inner-params il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L202-L203)
---
## sf-list
```clojure
(sf-list members)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L206-L207)
---
## sf-list?
```clojure
(sf-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L209-L210)
---
## list-members
```clojure
(list-members l)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L212-L213)
---
## sf-dict
```clojure
(sf-dict entries)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L216-L217)
---
## sf-dict?
```clojure
(sf-dict? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L219-L220)
---
## dict-keys
```clojure
(dict-keys d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L222-L223)
---
## dict-get
```clojure
(dict-get d k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L225-L226)
---
## dict->pairs
```clojure
(dict->pairs d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L228-L229)
---
## flag
```clojure
(flag)
(flag ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L232-L236)
---
## flag?
```clojure
(flag? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.1.x/src/ol/sfv.clj#L238-L239)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
* Removed references in docstrings to old functions that existed in an
unreleased version
* Reduced stdout during test suite execution
## `v0.1.0` (2025-09-08)
This is the first public release of this codebase under the `ol.sfv`
name.
Private variations of this code has been used in production for a
awhile, but nonetheless I want to be cautious so we’re starting with
sub-1.0 versioning.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
## ol.sfv
# ol.sfv
> A 0-dependency Clojure library for parsing and generating Structured
> Field Values for HTTP (RFC 9651/8941)



https://clojars.org/com.outskirtslabs/sfv[image:https://img.shields.io/clojars/v/com.outskirtslabs/sfv.svg[Clojars
Project]]
Structured Field Values (SFV) as defined in
[RFC 9651](https://datatracker.ietf.org/doc/html/rfc9651) provide a
standardized way to encode complex data structures in HTTP headers.
`ol.sfv` library implements the specification, providing parsing and
serialization capabilities.
This is a low-level library that emits and consumes the RFC 9651 AST,
which unfortunately does not map cleanly to Clojure datastructures. For
practical use, it should be wrapped in higher-level functions or
libraries that implement specific HTTP headers like
`Permissions-Policy`, `Signature-Input`, or `Signature` headers (any any
future HTTP headers).
Key features:
* Complete RFC 9651 implementation with AST-level parsing and
serialization with zero deps
* Extensively tested (`2853 tests, 3220 assertions`)
* Precise round-trip fidelity - parse and serialize back to identical
strings
* Validation and error reporting
* JVM/Graal target
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Installation
```clojure
{:deps {com.outskirtslabs/sfv {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/sfv "0.1.0"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.sfv :as sfv]))
;; Integer
(sfv/parse-item "42")
{:type :item :bare {:type :integer :value 42} :params []}
;; They are round trippable
(sfv/serialize-item {:type :item :bare {:type :integer :value 42}})
;; => "42"
;; Display String
(sfv/parse-item "%\"Gr%c3%bc%c3%9fe\"")
{:type :item :bare {:type :dstring :value "Grüße"} :params []}
(sfv/serialize-item {:type :item :bare {:type :dstring :value "السلام عليكم"} :params []})
"\"%d8%a7%d9%84%d8%b3%d9%84%d8%a7%d9%85 %d8%b9%d9%84%d9%8a%d9%83%d9%85\""
;; Dates
(sfv/parse-item "@1659578233")
{:type :item, :bare {:type :date, :value 1659578233}, :params []}
;; Items can have params attached
(sfv/parse-item "pear;sweet=?1")
{:type :item
:bare {:type :token :value "pear"}
:params [["sweet" {:type :token :value true}]]}
;; Parse a list with parameters
(sfv/parse-list "apple, pear;sweet=true, orange")
{:type :list
:members [{:type :item :bare {:type :token :value "apple"} :params []}
{:type :item :bare {:type :token :value "pear"} :params [["sweet" {:type :token :value "true"}]]}
{:type :item :bare {:type :token :value "orange"} :params []}]}
;; Dictionaries
(sfv/parse-dict "max-age=3600, must-revalidate")
{:type :dict
:entries [["max-age" {:type :item :bare {:type :integer :value 3600} :params []}]
["must-revalidate" {:type :item :bare {:type :boolean :value true} :params []}]]}
;; Dictionaries with inner lists and params
(sfv/parse-dict "trees=(\"spruce\";type=conifer \"oak\";type=deciduous)")
{:type :dict
:entries [["trees"
{:type :inner-list
:items [{:type :item :bare {:type :string :value "spruce"}
:params [["type" {:type :token :value "conifer"}]]}
{:type :item :bare {:type :string :value "oak"}
:params [["type" {:type :token :value "deciduous"}]]}]
:params []}]]}
(sfv/parse-dict "foods=(\"burger\";sandwich=?1 \"pizza\";sandwich=?0 \"hot dog\";sandwich=?1);comprehensive=?0")
{:type :dict
:entries [["foods" {:type :inner-list
:items [{:type :item :bare {:type :string :value "burger"}
:params [["sandwich" {:type :boolean :value true}]]}
{:type :item :bare {:type :string :value "pizza"}
:params [["sandwich" {:type :boolean :value false}]]}
{:type :item :bare {:type :string :value "hot dog"}
:params [["sandwich" {:type :boolean :value true}]]}]
:params [["comprehensive" {:type :boolean :value false}]]}]]}
;; List with Items and Inner List
(sfv/parse-list "circle;color=red, square;filled=?0, (triangle;size=3 rectangle;size=4)")
{:type :list
:members [{:type :item :bare {:type :token :value "circle"}
:params [["color" {:type :token :value "red"}]]}
{:type :item :bare {:type :token :value "square"}
:params [["filled" {:type :boolean :value false}]]}
{:type :inner-list :items [{:type :item :bare {:type :token :value "triangle"}
:params [["size" {:type :integer :value 3}]]}
{:type :item :bare {:type :token :value "rectangle"}
:params [["size" {:type :integer :value 4}]]}]
:params []}]}
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.sfv/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.sfv/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/sfv/issues)
## Data Types
`ol.sfv` is a low-level, AST-oriented implementation of RFC 9651. We
don’t try to coerce values into nice
Clojure shapes; instead we
expose a precise tree that round-trips byte-for-byte. This is important
because Structured Fields carry ordering information that ordinary
Clojure maps can’t reliably preserve across platforms and sizes.
### Primitive types
RFC 9651 defines several primitive types, all supported.
Each Item’s `:bare` is one of the following:
| | | | |
| --- | --- | --- | --- |
| SFV type | Header | AST example | Clojure type (`:value`) |
| Integer | `42`, `-17`, `999999999999999` | `++{++:type :integer :value 1618884473}` | `long` |
| Decimal | `3.14`, `-0.5` | `++{++:type :decimal :value 3.14M}` | `BigDecimal` |
| String | `"hello world"` | `++{++:type :string :value "hello"}` | `java.lang.String` |
| Token | `simple-token` | `++{++:type :token :value "simple-token"}` | `String` |
| Byte Sequence | `:SGVsbG8=:` | `+{++:type :bytes :value ++<++platform bytes++>++}` | `byte++[]+` |
| Boolean | `?1` / `?0` | `++{++:type :boolean :value true}` | `true` / `false` |
| Date | `@1659578233` | `++{++:type :date :value 1659578233}` | epoch seconds as `long` |
| Display String | `%"Gr%c3%bc%c3%9fe"` | `++{++:type :display :value "Grüße"}` | `String` (percent-decoded, validated) |
* Decimals obey SFV’s constraints (≤3 fractional digits, length limits)
and are parsed to `BigDecimal` to avoid float rounding.
* Byte sequences are base64 inside the header; we give you the decoded
bytes.
### Container types
* **Item** — a **bare value** plus optional **parameters**
```clojure
{:type :item
:bare
:params [ [param-name ] ... ]}
```
* **List** — a sequence of **items** or **inner lists**
```clojure
{:type :list
:members [ ... ]}
```
* **Dictionary** — an **ordered** sequence of key→member entries
```clojure
{:type :dict
:entries [ [key ] ... ]}
```
* **Inner List** — a parenthesized list (appears inside List/Dictionary)
with its own parameters
```clojure
{:type :inner-list
:items [ - ... ]
:params [ [param-name ] ... ]}
```
Keys are parsed as lower-case identifiers per the spec.
### Why not plain Clojure maps?
Dictionaries and parameter lists in SFV have a defined member order.
That order may or may not be semantically meaningful, that depends on
the specific header.
To make ordering explicit and stable, we represent dictionaries and
parameter lists as vectors of ++[++k v++]++ pairs. That’s portable,
preserves order, and lets you implement whatever key semantics you need
at a higher level.
### Parameters
Parameters attach metadata to an Item or an Inner List:
* Syntax: `;key++[++=value++]++` repeating after the base value If
`=value` is omitted, the parameter value is the boolean `true`.
* In the AST, parameters are always a vector of `+[++name value++]+`
pairs in the order seen:
```clojure
(sfv/parse-item "pear;sweet=?1")
{:type :item
:bare {:type :token :value "pear"}
:params [["sweet" {:type :boolean :value true}]]}
```
* Parameter values are bare values (not nested items). You’ll see the
same `:type`/`:value` shapes as the table above
* Ordering is preserved for round-trip fidelity
### Dictionaries
Dictionaries map from a key to either an Item or an Inner List. We keep
them as an ordered vector of entries:
```clojure
(sfv/parse-dict "max-age=3600, must-revalidate")
{:type :dict
:entries [["max-age" {:type :item :bare {:type :integer :value 3600} :params []}]
["must-revalidate" {:type :item :bare {:type :boolean :value true} :params []}]]}
```
## Building Header-Specific Libraries
This library is designed to be wrapped by more specific implementations:
```clojure
(ns myapp.cache-control
(:require [ol.sfv :as sfv]))
(defn parse-cache-control [header-value]
(let [parsed (sfv/parse-dict header-value)]
(reduce (fn [acc [key item]]
(let [value (get-in item [:bare :value])]
(assoc acc (keyword key) value)))
{}
(:entries parsed))))
(parse-cache-control "max-age=3600, must-revalidate")
;; => {:max-age 3600, :must-revalidate true}
```
## Recommended Reading
Structured Field Values provide a robust foundation for modern HTTP
header design:
* https://datatracker.ietf.org/doc/html/rfc9651[RFC 9651: Structured
Field Values for HTTP] - The complete specification
* https://datatracker.ietf.org/doc/html/rfc9421[RFC 9421: HTTP Message
Signatures] - A major consumer of structured fields
## Security
See [here](https://github.com/outskirtslabs/sfv/security) for security
advisories or to report a security vulnerability.
## License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT License](./LICENSE)
## ol.sfv
# ol.sfv
RFC 9651 Structured Field Values for HTTP.
See the project website for info on motivation and design:
<https://github.com/outskirtslabs/sfv>
This ns provides parsing and serialization of Structured Fields containing
Items, Lists, and Dictionaries with Parameters. Returns precise AST
representations that round-trip byte-for-byte with RFC 9651 strings.
Primary functions: [`parse`](#parse), [`parse-item`](#parse-item), [`parse-list`](#parse-list), [`parse-dict`](#parse-dict),
[`serialize`](#serialize)
## Conventions
* `s-or-bytes`: Input string or byte array (decoded as ascii) containing HTTP field value
* `field-type`: One of `:item`, `:list`, or `:dict` specifying the top-level structure
* `:bare`: The raw value within an Item (Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String)
## Primitive Types
Each Item’s `:bare` is one of these RFC 9651 types:
| | | | |
| --- | --- | --- | --- |
| SFV type | Header | AST example | Clojure type (`:value`) |
| Integer | `42`, `-17`, `999999999999999` | `{:type :integer :value 1618884473}` | `long` |
| Decimal | `3.14`, `-0.5` | `{:type :decimal :value 3.14M}` | `BigDecimal` |
| String | `" hello world "` | `{:type :string :value " hello "}` | `java.lang.String` |
| Token | `simple-token` | `{:type :token :value " simple-token "}` | `String` |
| Byte Sequence | `:SGVsbG8=:` | `{:type :bytes :value }` | `byte[]` |
| Boolean | `?1` / `?0` | `{:type :boolean :value true}` | `true` / `false` |
| Date | `@1659578233` | `{:type :date :value 1659578233}` | epoch seconds as `long` |
| Display String | `%" Gr%c3%bc%c3%9fe "` | `{:type :display :value " Grüße "}` | `String` (percent-decoded, validated) |
## parse
```clojure
(parse field-type s-or-bytes)
```
Parse a Structured Field of the given `field-type`.
Takes a `field-type` (`:list`, `:dict`, or `:item`) and a string or byte array.
Returns an AST representation following RFC 9651.
```clojure
(parse :item "42")
;; => {:type :item :bare {:type :integer :value 42} :params []}
(parse :dict "max-age=3600, must-revalidate")
;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L41-L55)
---
## parse-list
```clojure
(parse-list s-or-bytes)
```
Parse a Structured Field List from `s-or-bytes`.
Returns a List AST with `:type :list` and `:members` vector containing Items and Inner Lists.
```clojure
(parse-list "apple, pear;sweet=true, orange")
;; => {:type :list :members [...]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L57-L67)
---
## parse-dict
```clojure
(parse-dict s-or-bytes)
```
Parse a Structured Field Dictionary from `s-or-bytes`.
Returns a Dictionary AST with `:type :dict` and `:entries` as ordered key-value pairs.
Each entry maps from a key to either an Item or Inner List.
```clojure
(parse-dict "max-age=3600, must-revalidate")
;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L69-L80)
---
## parse-item
```clojure
(parse-item s-or-bytes)
```
Parse a Structured Field Item from `s-or-bytes`.
Returns an Item AST with `:type :item`, `:bare` value, and `:params` Parameters.
The bare value can be Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String.
```clojure
(parse-item "42")
;; => {:type :item :bare {:type :integer :value 42} :params []}
(parse-item "pear;sweet")
;; => {:type :item :bare {:type :token :value "pear"} :params `"sweet" {...}`}
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L82-L96)
---
## serialize
```clojure
(serialize x)
```
Serialize a Structured Field AST `x` to its string representation.
Takes any parsed AST (Item, List, or Dictionary) and returns the RFC 9651 string.
```clojure
(serialize {:type :item :bare {:type :integer :value 42} :params []})
;; => "42"
(serialize-list {:type :list :members [...]})
;; => "apple, pear;sweet=true, orange"
(serialize-dict {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]})
;; => "max-age=3600, must-revalidate"
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L98-L114)
---
## token
```clojure
(token s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L117-L118)
---
## token?
```clojure
(token? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L120-L121)
---
## decimal
```clojure
(decimal x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L123-L124)
---
## decimal?
```clojure
(decimal? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L125-L126)
---
## integer
```clojure
(integer n)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L128-L129)
---
## integer?
```clojure
(integer? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L131-L132)
---
## string
```clojure
(string s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L134-L135)
---
## string?
```clojure
(string? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L137-L138)
---
## dstring
```clojure
(dstring s)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L140-L141)
---
## dstring?
```clojure
(dstring? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L143-L144)
---
## bytes
```clojure
(bytes b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L146-L147)
---
## bytes?
```clojure
(bytes? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L149-L150)
---
## bool
```clojure
(bool b)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L152-L153)
---
## bool?
```clojure
(bool? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L155-L156)
---
## date
```clojure
(date seconds)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L158-L159)
---
## date?
```clojure
(date? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L161-L162)
---
## params
```clojure
(params & kvs)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L165-L166)
---
## param-get
```clojure
(param-get ps k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L168-L169)
---
## param-keys
```clojure
(param-keys ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L171-L172)
---
## item
```clojure
(item bare)
(item bare ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L175-L179)
---
## item?
```clojure
(item? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L181-L182)
---
## item-bare
```clojure
(item-bare i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L184-L185)
---
## item-params
```clojure
(item-params i)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L187-L188)
---
## inner-list
```clojure
(inner-list items)
(inner-list items ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L191-L195)
---
## inner-list?
```clojure
(inner-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L197-L198)
---
## inner-items
```clojure
(inner-items il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L200-L201)
---
## inner-params
```clojure
(inner-params il)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L203-L204)
---
## sf-list
```clojure
(sf-list members)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L207-L208)
---
## sf-list?
```clojure
(sf-list? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L210-L211)
---
## list-members
```clojure
(list-members l)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L213-L214)
---
## sf-dict
```clojure
(sf-dict entries)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L217-L218)
---
## sf-dict?
```clojure
(sf-dict? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L220-L221)
---
## dict-keys
```clojure
(dict-keys d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L223-L224)
---
## dict-get
```clojure
(dict-get d k)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L226-L227)
---
## dict->pairs
```clojure
(dict->pairs d)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L229-L230)
---
## flag
```clojure
(flag)
(flag ps)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L233-L237)
---
## flag?
```clojure
(flag? x)
```
[source,window=_blank](https://github.com/outskirtslabs/sfv/blob/main/src/ol/sfv.clj#L239-L240)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
* Removed references in docstrings to old functions that existed in an
unreleased version
* Reduced stdout during test suite execution
## `v0.1.0` (2025-09-08)
This is the first public release of this codebase under the `ol.sfv`
name.
Private variations of this code has been used in production for a
awhile, but nonetheless I want to be cautious so we’re starting with
sub-1.0 versioning.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
## ol.sfv
# ol.sfv
> A 0-dependency Clojure library for parsing and generating Structured
> Field Values for HTTP (RFC 9651/8941)



https://clojars.org/com.outskirtslabs/sfv[image:https://img.shields.io/clojars/v/com.outskirtslabs/sfv.svg[Clojars
Project]]
Structured Field Values (SFV) as defined in
[RFC 9651](https://datatracker.ietf.org/doc/html/rfc9651) provide a
standardized way to encode complex data structures in HTTP headers.
`ol.sfv` library implements the specification, providing parsing and
serialization capabilities.
This is a low-level library that emits and consumes the RFC 9651 AST,
which unfortunately does not map cleanly to Clojure datastructures. For
practical use, it should be wrapped in higher-level functions or
libraries that implement specific HTTP headers like
`Permissions-Policy`, `Signature-Input`, or `Signature` headers (any any
future HTTP headers).
Key features:
* Complete RFC 9651 implementation with AST-level parsing and
serialization with zero deps
* Extensively tested (`2853 tests, 3220 assertions`)
* Precise round-trip fidelity - parse and serialize back to identical
strings
* Validation and error reporting
* JVM/Graal target
Project status: **[Stable](https://docs.outskirtslabs.com/open-source-vital-signs#stable)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/sfv {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/sfv "0.1.0"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.sfv :as sfv]))
;; Integer
(sfv/parse-item "42")
{:type :item :bare {:type :integer :value 42} :params []}
;; They are round trippable
(sfv/serialize {:type :item :bare {:type :integer :value 42}})
;; => "42"
;; Display String
(sfv/parse-item "%\"Gr%c3%bc%c3%9fe\"")
{:type :item :bare {:type :dstring :value "Grüße"} :params []}
(sfv/serialize {:type :item :bare {:type :dstring :value "السلام عليكم"} :params []})
"\"%d8%a7%d9%84%d8%b3%d9%84%d8%a7%d9%85 %d8%b9%d9%84%d9%8a%d9%83%d9%85\""
;; Dates
(sfv/parse-item "@1659578233")
{:type :item, :bare {:type :date, :value 1659578233}, :params []}
;; Items can have params attached
(sfv/parse-item "pear;sweet=?1")
{:type :item
:bare {:type :token :value "pear"}
:params [["sweet" {:type :token :value true}]]}
;; Parse a list with parameters
(sfv/parse-list "apple, pear;sweet=true, orange")
{:type :list
:members [{:type :item :bare {:type :token :value "apple"} :params []}
{:type :item :bare {:type :token :value "pear"} :params [["sweet" {:type :token :value "true"}]]}
{:type :item :bare {:type :token :value "orange"} :params []}]}
;; Dictionaries
(sfv/parse-dict "max-age=3600, must-revalidate")
{:type :dict
:entries [["max-age" {:type :item :bare {:type :integer :value 3600} :params []}]
["must-revalidate" {:type :item :bare {:type :boolean :value true} :params []}]]}
;; Dictionaries with inner lists and params
(sfv/parse-dict "trees=(\"spruce\";type=conifer \"oak\";type=deciduous)")
{:type :dict
:entries [["trees"
{:type :inner-list
:items [{:type :item :bare {:type :string :value "spruce"}
:params [["type" {:type :token :value "conifer"}]]}
{:type :item :bare {:type :string :value "oak"}
:params [["type" {:type :token :value "deciduous"}]]}]
:params []}]]}
(sfv/parse-dict "foods=(\"burger\";sandwich=?1 \"pizza\";sandwich=?0 \"hot dog\";sandwich=?1);comprehensive=?0")
{:type :dict
:entries [["foods" {:type :inner-list
:items [{:type :item :bare {:type :string :value "burger"}
:params [["sandwich" {:type :boolean :value true}]]}
{:type :item :bare {:type :string :value "pizza"}
:params [["sandwich" {:type :boolean :value false}]]}
{:type :item :bare {:type :string :value "hot dog"}
:params [["sandwich" {:type :boolean :value true}]]}]
:params [["comprehensive" {:type :boolean :value false}]]}]]}
;; List with Items and Inner List
(sfv/parse-list "circle;color=red, square;filled=?0, (triangle;size=3 rectangle;size=4)")
{:type :list
:members [{:type :item :bare {:type :token :value "circle"}
:params [["color" {:type :token :value "red"}]]}
{:type :item :bare {:type :token :value "square"}
:params [["filled" {:type :boolean :value false}]]}
{:type :inner-list :items [{:type :item :bare {:type :token :value "triangle"}
:params [["size" {:type :integer :value 3}]]}
{:type :item :bare {:type :token :value "rectangle"}
:params [["size" {:type :integer :value 4}]]}]
:params []}]}
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.sfv/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.sfv/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/sfv/issues)
## Data Types
`ol.sfv` is a low-level, AST-oriented implementation of RFC 9651. We
don’t try to coerce values into
nice
Clojure shapes; instead we
expose a precise tree that round-trips byte-for-byte. This is important
because Structured Fields carry ordering information that ordinary
Clojure maps can’t reliably preserve across platforms and sizes.
### Primitive types
RFC 9651 defines several primitive types, all supported.
Each Item’s `:bare` is one of the following:
| | | | |
| --- | --- | --- | --- |
| SFV type | Header | AST example | Clojure type (`:value`) |
| Integer | `42`, `-17`, `999999999999999` | `++{++:type :integer :value 1618884473}` | `long` |
| Decimal | `3.14`, `-0.5` | `++{++:type :decimal :value 3.14M}` | `BigDecimal` |
| String | `"hello world"` | `++{++:type :string :value "hello"}` | `java.lang.String` |
| Token | `simple-token` | `++{++:type :token :value "simple-token"}` | `String` |
| Byte Sequence | `:SGVsbG8=:` | `+{++:type :bytes :value ++<++platform bytes++>++}` | `byte++[]+` |
| Boolean | `?1` / `?0` | `++{++:type :boolean :value true}` | `true` / `false` |
| Date | `@1659578233` | `++{++:type :date :value 1659578233}` | epoch seconds as `long` |
| Display String | `%"Gr%c3%bc%c3%9fe"` | `++{++:type :display :value "Grüße"}` | `String` (percent-decoded, validated) |
* Decimals obey SFV’s constraints (≤3 fractional digits, length limits)
and are parsed to `BigDecimal` to avoid float rounding.
* Byte sequences are base64 inside the header; we give you the decoded
bytes.
### Container types
* **Item** — a **bare value** plus optional **parameters**
```clojure
{:type :item
:bare
:params [ [param-name ] ... ]}
```
* **List** — a sequence of **items** or **inner lists**
```clojure
{:type :list
:members [ ... ]}
```
* **Dictionary** — an **ordered** sequence of key→member entries
```clojure
{:type :dict
:entries [ [key ] ... ]}
```
* **Inner List** — a parenthesized list (appears inside List/Dictionary)
with its own parameters
```clojure
{:type :inner-list
:items [ - ... ]
:params [ [param-name ] ... ]}
```
Keys are parsed as lower-case identifiers per the spec.
### Why not plain Clojure maps?
Dictionaries and parameter lists in SFV have a defined member order.
That order may or may not be semantically meaningful, that depends on
the specific header.
To make ordering explicit and stable, we represent dictionaries and
parameter lists as vectors of ++[++k v++]++ pairs. That’s portable,
preserves order, and lets you implement whatever key semantics you need
at a higher level.
### Parameters
Parameters attach metadata to an Item or an Inner List:
* Syntax: `;key++[++=value++]++` repeating after the base value If
`=value` is omitted, the parameter value is the boolean `true`.
* In the AST, parameters are always a vector of `+[++name value++]+`
pairs in the order seen:
```clojure
(sfv/parse-item "pear;sweet=?1")
{:type :item
:bare {:type :token :value "pear"}
:params [["sweet" {:type :boolean :value true}]]}
```
* Parameter values are bare values (not nested items). You’ll see the
same `:type`/`:value` shapes as the table above
* Ordering is preserved for round-trip fidelity
### Dictionaries
Dictionaries map from a key to either an Item or an Inner List. We keep
them as an ordered vector of entries:
```clojure
(sfv/parse-dict "max-age=3600, must-revalidate")
{:type :dict
:entries [["max-age" {:type :item :bare {:type :integer :value 3600} :params []}]
["must-revalidate" {:type :item :bare {:type :boolean :value true} :params []}]]}
```
## Building Header-Specific Libraries
This library is designed to be wrapped by more specific implementations:
```clojure
(ns myapp.cache-control
(:require [ol.sfv :as sfv]))
(defn parse-cache-control [header-value]
(let [parsed (sfv/parse-dict header-value)]
(reduce (fn [acc [key item]]
(let [value (get-in item [:bare :value])]
(assoc acc (keyword key) value)))
{}
(:entries parsed))))
(parse-cache-control "max-age=3600, must-revalidate")
;; => {:max-age 3600, :must-revalidate true}
```
## Recommended Reading
Structured Field Values provide a robust foundation for modern HTTP
header design:
* https://datatracker.ietf.org/doc/html/rfc9651[RFC 9651: Structured
Field Values for HTTP] - The complete specification
* https://datatracker.ietf.org/doc/html/rfc9421[RFC 9421: HTTP Message
Signatures] - A major consumer of structured fields
## Security
See [here](https://github.com/outskirtslabs/sfv/security) for security
advisories or to report a security vulnerability.
## License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT License](./LICENSE)
## ol.sops
# ol.sops
## sops
```clojure
(sops cmd)
(sops cmd args-or-opts)
(sops cmd args & opts)
```
A simple wrapper around babashka.process for calling sops.
You can call `sops` like this:
(sops :command ["args vector"] :opt "value" :opt2 "value2")
There are several special opts, that are handled by ol.sops:
:in - An input stream than can be passed to sops
:out - :string if you want to get a string back, nil (the default) will return an output stream you can slurp
:dir - The working directory. Defaults to the current working directory
:sops-path - Path to the sops binary. Defaults to `sops`
:env - a map of environment variables that are passed to the process
All other opts are passed to the `sops` CLI. Consult `sops CMD --help` to see available options.
To pass flags like `--verbose` use `:verbose true`. Always use full names of the options, not the short versions like -v.
Refer to `babashka.process/process` for the exact behavior of: :in, :out, :dir, and the exact nature of the return value.
Returns a record with (among other things, see `babashka.process/process`):
- `:out` the output stream
- `:exit` the exit code of sops
- `:err` a string containing the stderr output from sops, if any
If you `clojure.core/deref` the record, then it will block until the process has exited.
If you slurp from the out stream, it will also block until the process has exited.
Usage examples
--------------
Decrypt file in place
@(sops :decrypt ["secrets.sops.yaml"] :in-place)
Decrypt file to a string
(slurp (:out (sops :decrypt ["secrets.sops.yaml"])))
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/v0.1.x/src/ol/sops.clj#L6-L46)
---
## decrypt-file-to-str
```clojure
(decrypt-file-to-str file opts)
```
Decrypts the path `file` and returns the output as a string. Sugar over [`sops`](#sops).
Options:
- `:input-type` - Override file type detection (yaml, json, env, ini, binary)
- `:output-type` - Override output format (yaml, json, env, ini, binary)
- Other SOPS options as documented in `sops decrypt --help`
Example:
```clojure
(decrypt-file "secrets.sops.yaml" {})
(decrypt-file "secrets.enc" {:input-type "yaml" :output-type "json"})
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/v0.1.x/src/ol/sops.clj#L48-L62)
---
## encrypt-to-file
```clojure
(encrypt-to-file file plaintext opts)
```
Encrypts `plaintext` string and writes encrypted content to `file`. Sugar over [`sops`](#sops).
The `file` path is used to match creation rules in `.sops.yaml` and determine the file format.
Options:
- `:age` - Age recipient(s) to encrypt for (can be a single string or vector of strings)
- `:pgp` - PGP fingerprint(s) to encrypt for (can be a single string or vector of strings)
- `:gcp-kms` - GCP KMS resource ID(s) to encrypt with (can be a single string or vector of strings)
- `:azure-kv` - Azure Key Vault URL(s) to encrypt with (can be a single string or vector of strings)
- `:kms` - AWS KMS ARN(s) to encrypt with (can be a single string or vector of strings)
- `:aws-profile` - The AWS profile to use for requests to AWS
- Other SOPS options as documented in `sops encrypt --help`
Example:
```clojure
(encrypt-to-file "secrets.sops.yaml" "foo: bar" {:age "age1..."})
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/v0.1.x/src/ol/sops.clj#L64-L83)
---
## with-process-opts
_macro_
```clojure
(with-process-opts env-map & body)
```
An escape hatch that executes body with additional babashka/process options merged into the process call.
Example:
```clojure
;; Override the environemnt
(with-process-opts {:extra-env {:SOPS_AGE_KEY_FILE "/some/path/to/keys.txt"}}
(sops :decrypt ["secrets.yaml"]))
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/v0.1.x/src/ol/sops.clj#L85-L96)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
## `v0.1.0` (2025-10-03)
This is the first public release of this codebase under the `ol.sops`
name.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
##
ol.sops
# `ol.sops`
> An extremely tiny and simple wrapper around the awesome sops (previously
> known as mozilla/sops)



https://clojars.org/com.outskirtslabs/sops[image:https://img.shields.io/clojars/v/com.outskirtslabs/sops.svg[Clojars
Project]]
This is intended for [babashka](https://babashka.org/) and JVM clojure and
provides an idiomatic and data driven wrapper around the CLI tool.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Installation
```clojure
{:deps {com.outskirtslabs/sops {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/sops "0.1.0"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.sops :as sops]))
;; decrypt a sops file to string
(sops/decrypt-file-to-str "dev/test.sops.yml"
{:env {"SOPS_AGE_KEY_FILE" "dev/keys.txt"}})
;; => "hello: world\n"
;; encrypt a plaintext to a sfile
@(sops/encrypt-to-file "output.sops.json"
(edn->json {:foo "bar"})
{:age "age15905pjs5av9nyh8rdt4zrzn7x0mdud20eyf7tsvz63mygvsfhd9sclsh94"})
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.sops/0.1/)
* [API Reference](https://docs.outskirtslabs.com/ol.sops/0.1/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/sops/issues)
## Security
See [here](https://github.com/outskirtslabs/sops/security) for security
advisories or to report a security vulnerability.
## License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT License](./LICENSE)
## Security policy
# Security policy
## Advisories
All security advisories will be posted
https://github.com/outskirtslabs/sops/security/advisories[on
GitHub].
## Reporting a vulnerability
Please report possible security vulnerabilities
https://github.com/outskirtslabs/sops/security/advisories[via
GitHub], or by emailing me at `casey@outskirtslabs.com`. You may encrypt
emails with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
— [Casey Link](https://casey.link)
## ol.sops
# ol.sops
## sops
```clojure
(sops cmd)
(sops cmd args-or-opts)
(sops cmd args & opts)
```
A simple wrapper around babashka.process for calling sops.
You can call `sops` like this:
(sops :command ["args vector"] :opt "value" :opt2 "value2")
There are several special opts, that are handled by ol.sops:
:in - An input stream than can be passed to sops
:out - :string if you want to get a string back, nil (the default) will return an output stream you can slurp
:dir - The working directory. Defaults to the current working directory
:sops-path - Path to the sops binary. Defaults to `sops`
:env - a map of environment variables that are passed to the process
All other opts are passed to the `sops` CLI. Consult `sops CMD --help` to see available options.
To pass flags like `--verbose` use `:verbose true`. Always use full names of the options, not the short versions like -v.
Refer to `babashka.process/process` for the exact behavior of: :in, :out, :dir, and the exact nature of the return value.
Returns a record with (among other things, see `babashka.process/process`):
- `:out` the output stream
- `:exit` the exit code of sops
- `:err` a string containing the stderr output from sops, if any
If you `clojure.core/deref` the record, then it will block until the process has exited.
If you slurp from the out stream, it will also block until the process has exited.
Usage examples
--------------
Decrypt file in place
@(sops :decrypt ["secrets.sops.yaml"] :in-place)
Decrypt file to a string
(slurp (:out (sops :decrypt ["secrets.sops.yaml"])))
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/main/src/ol/sops.clj#L6-L46)
---
## decrypt-file-to-str
```clojure
(decrypt-file-to-str file opts)
```
Decrypts the path `file` and returns the output as a string. Sugar over [`sops`](#sops).
Options:
- `:input-type` - Override file type detection (yaml, json, env, ini, binary)
- `:output-type` - Override output format (yaml, json, env, ini, binary)
- Other SOPS options as documented in `sops decrypt --help`
Example:
```clojure
(decrypt-file "secrets.sops.yaml" {})
(decrypt-file "secrets.enc" {:input-type "yaml" :output-type "json"})
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/main/src/ol/sops.clj#L48-L62)
---
## encrypt-to-file
```clojure
(encrypt-to-file file plaintext opts)
```
Encrypts `plaintext` string and writes encrypted content to `file`. Sugar over [`sops`](#sops).
The `file` path is used to match creation rules in `.sops.yaml` and determine the file format.
Options:
- `:age` - Age recipient(s) to encrypt for (can be a single string or vector of strings)
- `:pgp` - PGP fingerprint(s) to encrypt for (can be a single string or vector of strings)
- `:gcp-kms` - GCP KMS resource ID(s) to encrypt with (can be a single string or vector of strings)
- `:azure-kv` - Azure Key Vault URL(s) to encrypt with (can be a single string or vector of strings)
- `:kms` - AWS KMS ARN(s) to encrypt with (can be a single string or vector of strings)
- `:aws-profile` - The AWS profile to use for requests to AWS
- Other SOPS options as documented in `sops encrypt --help`
Example:
```clojure
(encrypt-to-file "secrets.sops.yaml" "foo: bar" {:age "age1..."})
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/main/src/ol/sops.clj#L64-L83)
---
## with-process-opts
_macro_
```clojure
(with-process-opts env-map & body)
```
An escape hatch that executes body with additional babashka/process options merged into the process call.
Example:
```clojure
;; Override the environemnt
(with-process-opts {:extra-env {:SOPS_AGE_KEY_FILE "/some/path/to/keys.txt"}}
(sops :decrypt ["secrets.yaml"]))
```
[source,window=_blank](https://github.com/outskirtslabs/sops/blob/main/src/ol/sops.clj#L85-L96)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses https://www.taoensso.com/break-versioning[*Break
Versioning*].
## ++[++UNRELEASED++]++
## `v0.1.0` (2025-10-03)
This is the first public release of this codebase under the `ol.sops`
name.
Please report any problems and let me know if anything is unclear,
inconvenient, etc. Thank you! 🙏
## ol.sops
# `ol.sops`
> An extremely tiny and simple wrapper around the awesome sops (previously
> known as mozilla/sops)



https://clojars.org/com.outskirtslabs/sops[image:https://img.shields.io/clojars/v/com.outskirtslabs/sops.svg[Clojars
Project]]
This is intended for [babashka](https://babashka.org/) and JVM clojure and
provides an idiomatic and data driven wrapper around the CLI tool.
Project status: **[Maturing](https://docs.outskirtslabs.com/open-source-vital-signs#maturing)**.
## Installation
```clojure
;; deps.edn
{:deps {com.outskirtslabs/sops {:mvn/version "0.1.0"}}}
;; Leiningen
[com.outskirtslabs/sops "0.1.0"]
```
## Quick Start
```clojure
(ns myapp.core
(:require [ol.sops :as sops]))
;; decrypt a sops file to string
(sops/decrypt-file-to-str "dev/test.sops.yml"
{:env {"SOPS_AGE_KEY_FILE" "dev/keys.txt"}})
;; => "hello: world\n"
;; encrypt a plaintext to a sfile
@(sops/encrypt-to-file "output.sops.json"
(edn->json {:foo "bar"})
{:age "age15905pjs5av9nyh8rdt4zrzn7x0mdud20eyf7tsvz63mygvsfhd9sclsh94"})
```
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.sops/next/)
* [API Reference](https://docs.outskirtslabs.com/ol.sops/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/sops/issues)
## Security
See [here](https://github.com/outskirtslabs/sops/security) for security
advisories or to report a security vulnerability.
## License
Copyright © 2025 Casey Link casey@outskirtslabs.com
Distributed under the [MIT License](./LICENSE)
## Security policy
# Security policy
## Advisories
All security advisories will be posted
https://github.com/outskirtslabs/sops/security/advisories[on
GitHub].
## Reporting a vulnerability
Please report possible security vulnerabilities
https://github.com/outskirtslabs/sops/security/advisories[via
GitHub], or by emailing me at `casey@outskirtslabs.com`. You may encrypt
emails with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see
[Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
— [Casey Link](https://casey.link)
## ol.trixnity.client
# ol.trixnity.client
Matrix client lifecycle, sync control, and client-level state flows.
## Upstream Mapping
This namespace maps to client-wide APIs on Trixnity’s
`de.connect2x.trixnity.client.MatrixClient`.
The public wrappers here cover:
* opening or resuming a client with [`open`](#open)
* sync lifecycle tasks such as [`start-sync`](#start-sync), [`await-running`](#await-running),
[`stop-sync`](#stop-sync), and [`close`](#close)
* synchronous `current-*` accessors paired with relieved Missionary flows
for profile, server data, sync state, login state, and startup state
Use [`ol.trixnity.repo`](api/ol-trixnity-repo.adoc) when you want the built-in sqlite4clj-backed
repository setup, and [`ol.trixnity.room`](api/ol-trixnity-room.adoc), [`ol.trixnity.user`](api/ol-trixnity-user.adoc),
[`ol.trixnity.notification`](api/ol-trixnity-notification.adoc), [`ol.trixnity.verification`](api/ol-trixnity-verification.adoc), and
[`ol.trixnity.key`](api/ol-trixnity-key.adoc) for service-specific APIs.
## open
```clojure
(open config)
```
Opens or resumes a `MatrixClient` using the built-in sqlite4clj-backed
repository configuration.
If `config` already contains a client under `::mx/client`, the returned task
resolves to that client immediately.
Supported config keys include:
* `::mx/homeserver-url`
* `::mx/user-id`
* `::mx/password`
* `::mx/device-name` optional initial Matrix device display name
* `::mx/device-id` optional Matrix device id to request at login
* `::mx/database-path`
* `::mx/media-path`
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L39-L60)
---
## start-sync
```clojure
(start-sync client)
```
Starts sync for `client` and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L62-L65)
---
## await-running
```clojure
(await-running client)
(await-running client opts)
```
Waits for `client` to reach `RUNNING`.
Supported opts:
`{::mx/timeout java.time.Duration}`
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L67-L79)
---
## stop-sync
```clojure
(stop-sync client)
```
Stops sync for `client` and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L81-L84)
---
## close
```clojure
(close client)
```
Closes `client` and tears down its bridge-owned coroutine scope.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L86-L89)
---
## current-user-id
```clojure
(current-user-id client)
```
Returns the Matrix user id of `client` as a string.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L91-L94)
---
## current-sync-state
```clojure
(current-sync-state client)
```
Returns the current sync state as a lower-case keyword.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L96-L99)
---
## current-profile
```clojure
(current-profile client)
```
Returns the current normalized client profile.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L101-L104)
---
## profile
```clojure
(profile client)
```
Returns a Missionary flow of the current normalized client profile.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L106-L110)
---
## current-server-data
```clojure
(current-server-data client)
```
Returns the current normalized server data snapshot.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L112-L115)
---
## server-data
```clojure
(server-data client)
```
Returns a Missionary flow of normalized server data snapshots.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L117-L121)
---
## sync-state
```clojure
(sync-state client)
```
Returns a Missionary flow of the current sync state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L123-L128)
---
## current-initial-sync-done
```clojure
(current-initial-sync-done client)
```
Returns whether initial sync has completed.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L130-L133)
---
## initial-sync-done
```clojure
(initial-sync-done client)
```
Returns a Missionary flow of initial-sync completion state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L135-L139)
---
## current-login-state
```clojure
(current-login-state client)
```
Returns the current login state as a lower-case keyword or nil.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L141-L144)
---
## login-state
```clojure
(login-state client)
```
Returns a Missionary flow of lower-case login-state keywords or nil.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L146-L151)
---
## current-started
```clojure
(current-started client)
```
Returns whether sync has been started for `client`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L153-L156)
---
## started
```clojure
(started client)
```
Returns a Missionary flow of `client` started state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj#L158-L162)
## ol.trixnity.event
# ol.trixnity.event
Accessors and predicates for normalized event maps.
This namespace provides convenience accessors over
the normalized event shapes defined in [`ol.trixnity.schemas`](api/ol-trixnity-schemas.adoc) without
exposing callers to raw bridge internals.
Use these helpers when consuming timeline events, notifications, or reply
metadata from [`ol.trixnity.room`](api/ol-trixnity-room.adoc), [`ol.trixnity.notification`](api/ol-trixnity-notification.adoc), and
[`ol.trixnity.room.message`](api/ol-trixnity-room-message.adoc).
## type
```clojure
(type event)
```
Returns the Matrix event type string from normalized `event`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L17-L20)
---
## room-id
```clojure
(room-id event)
```
Returns the Matrix room id string from normalized `event`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L22-L25)
---
## event-id
```clojure
(event-id event)
```
Returns the Matrix event id string from normalized `event`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L27-L30)
---
## sender
```clojure
(sender event)
```
Returns the sender user id string from normalized `event`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L32-L35)
---
## sender-display-name
```clojure
(sender-display-name event)
```
Returns the sender display name from normalized `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L37-L40)
---
## body
```clojure
(body event)
```
Returns the normalized message body from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L42-L45)
---
## msgtype
```clojure
(msgtype event)
```
Returns the Matrix room-message subtype from normalized `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L47-L50)
---
## key
```clojure
(key event)
```
Returns the normalized reaction key or relation key from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L52-L55)
---
## relates-to
```clojure
(relates-to event)
```
Returns the normalized `::mx/relates-to` map from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L57-L60)
---
## relation-event-id
```clojure
(relation-event-id event)
```
Returns the related event id from `event` reply or relation metadata, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L62-L65)
---
## raw
```clojure
(raw event)
```
Returns the upstream raw event object carried by normalized `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L67-L70)
---
## url
```clojure
(url event)
```
Returns the normalized MXC URI from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L72-L75)
---
## encrypted-file
```clojure
(encrypted-file event)
```
Returns the normalized encrypted-file map from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L77-L80)
---
## file-name
```clojure
(file-name event)
```
Returns the normalized file name from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L82-L85)
---
## mime-type
```clojure
(mime-type event)
```
Returns the normalized MIME type from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L87-L90)
---
## size-bytes
```clojure
(size-bytes event)
```
Returns the normalized byte size from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L92-L95)
---
## duration
```clojure
(duration event)
```
Returns the normalized `java.time.Duration` from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L97-L100)
---
## height
```clojure
(height event)
```
Returns the normalized media height from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L102-L105)
---
## width
```clojure
(width event)
```
Returns the normalized media width from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L107-L110)
---
## thumbnail-url
```clojure
(thumbnail-url event)
```
Returns the event-provided thumbnail MXC URI from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L112-L115)
---
## thumbnail-encrypted-file
```clojure
(thumbnail-encrypted-file event)
```
Returns the event-provided encrypted thumbnail map from `event`, if present.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L117-L120)
---
## text?
```clojure
(text? event)
```
Returns true when `event` is a Matrix room-message event.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L122-L125)
---
## reaction?
```clojure
(reaction? event)
```
Returns true when `event` is a Matrix reaction event.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/event.clj#L127-L130)
## ol.trixnity.internal.bridge
# ol.trixnity.internal.bridge
## open-client
```clojure
(open-client request on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L19-L20)
---
## start-sync
```clojure
(start-sync client on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L22-L23)
---
## await-running
```clojure
(await-running client timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L25-L26)
---
## stop-sync
```clojure
(stop-sync client on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L28-L29)
---
## close-client
```clojure
(close-client client on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L31-L32)
---
## current-user-id
```clojure
(current-user-id client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L34-L35)
---
## current-sync-state
```clojure
(current-sync-state client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L37-L38)
---
## current-profile
```clojure
(current-profile client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L40-L41)
---
## profile-flow
```clojure
(profile-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L43-L44)
---
## current-server-data
```clojure
(current-server-data client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L46-L47)
---
## server-data-flow
```clojure
(server-data-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L49-L50)
---
## sync-state-flow
```clojure
(sync-state-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L52-L53)
---
## current-initial-sync-done
```clojure
(current-initial-sync-done client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L55-L56)
---
## initial-sync-done-flow
```clojure
(initial-sync-done-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L58-L59)
---
## current-login-state
```clojure
(current-login-state client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L61-L62)
---
## login-state-flow
```clojure
(login-state-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L64-L65)
---
## current-started
```clojure
(current-started client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L67-L68)
---
## started-flow
```clojure
(started-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L70-L71)
---
## current-users-typing
```clojure
(current-users-typing client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L73-L74)
---
## users-typing-flow
```clojure
(users-typing-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L76-L77)
---
## set-typing
```clojure
(set-typing client room-id typing timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L79-L80)
---
## create-room
```clojure
(create-room client request on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L82-L83)
---
## invite-user
```clojure
(invite-user client room-id user-id timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L85-L86)
---
## kick-user
```clojure
(kick-user client room-id user-id reason timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L88-L89)
---
## ban-user
```clojure
(ban-user client room-id user-id reason timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L91-L92)
---
## unban-user
```clojure
(unban-user client room-id user-id reason timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L94-L95)
---
## create-space
```clojure
(create-space client request timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L97-L98)
---
## space-hierarchy
```clojure
(space-hierarchy client space-id request timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L100-L101)
---
## spaces
```clojure
(spaces client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L103-L104)
---
## spaces-flat
```clojure
(spaces-flat client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L106-L107)
---
## space-child
```clojure
(space-child client space-id child-room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L109-L110)
---
## space-children
```clojure
(space-children client space-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L112-L113)
---
## space-parents
```clojure
(space-parents client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L115-L116)
---
## set-space-child
```clojure
(set-space-child client space-id child-room-id content timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L118-L119)
---
## remove-space-child
```clojure
(remove-space-child client space-id child-room-id timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L121-L122)
---
## set-space-parent
```clojure
(set-space-parent client room-id parent-space-id content timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L124-L125)
---
## remove-space-parent
```clojure
(remove-space-parent client room-id parent-space-id timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L127-L128)
---
## join-room
```clojure
(join-room client room-id-or-alias timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L130-L131)
---
## leave-room
```clojure
(leave-room client room-id reason timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L133-L134)
---
## forget-room
```clojure
(forget-room client room-id force on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L136-L137)
---
## send-message
```clojure
(send-message client room-id message timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L139-L140)
---
## send-reaction
```clojure
(send-reaction client room-id event-id key timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L142-L143)
---
## send-state-event
```clojure
(send-state-event client room-id state-event timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L145-L146)
---
## set-power-levels
```clojure
(set-power-levels client room-id power-levels timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L148-L149)
---
## redact-event
```clojure
(redact-event client room-id event-id reason timeout on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L151-L152)
---
## cancel-send-message
```clojure
(cancel-send-message client room-id transaction-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L154-L155)
---
## retry-send-message
```clojure
(retry-send-message client room-id transaction-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L157-L158)
---
## room-by-id
```clojure
(room-by-id client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L160-L161)
---
## rooms
```clojure
(rooms client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L163-L164)
---
## rooms-flat
```clojure
(rooms-flat client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L166-L167)
---
## account-data
```clojure
(account-data client room-id event-content-class key)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L169-L170)
---
## state
```clojure
(state client room-id event-content-class state-key)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L172-L173)
---
## power-levels
```clojure
(power-levels client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L175-L176)
---
## all-state
```clojure
(all-state client room-id event-content-class)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L178-L179)
---
## outbox
```clojure
(outbox client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L181-L182)
---
## outbox-flat
```clojure
(outbox-flat client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L184-L185)
---
## outbox-by-room
```clojure
(outbox-by-room client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L187-L188)
---
## outbox-by-room-flat
```clojure
(outbox-by-room-flat client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L190-L191)
---
## outbox-message
```clojure
(outbox-message client room-id transaction-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L193-L194)
---
## fill-timeline-gaps
```clojure
(fill-timeline-gaps client room-id event-id limit on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L196-L197)
---
## timeline-events-from-now-on
```clojure
(timeline-events-from-now-on client decryption-timeout-ms sync-response-buffer-size)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L199-L204)
---
## response-timeline-events
```clojure
(response-timeline-events client response decryption-timeout-ms)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L206-L207)
---
## prepare-upload-media
```clojure
(prepare-upload-media client source-path mime-type on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L209-L210)
---
## upload-media
```clojure
(upload-media client cache-uri keep-in-cache on-progress on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L212-L213)
---
## get-media
```clojure
(get-media client uri on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L215-L216)
---
## get-encrypted-media
```clojure
(get-encrypted-media client encrypted-file on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L218-L219)
---
## get-thumbnail
```clojure
(get-thumbnail client uri width height method animated on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L221-L223)
---
## media-temporary-file
```clojure
(media-temporary-file platform-media on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L225-L226)
---
## delete-media-temporary-file
```clojure
(delete-media-temporary-file temporary-file)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L228-L229)
---
## timeline-event
```clojure
(timeline-event client room-id event-id decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L231-L240)
---
## previous-timeline-event
```clojure
(previous-timeline-event client timeline-event decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L242-L250)
---
## next-timeline-event
```clojure
(next-timeline-event client timeline-event decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L252-L260)
---
## last-timeline-event
```clojure
(last-timeline-event client room-id decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L262-L270)
---
## timeline-event-chain
```clojure
(timeline-event-chain client room-id start-from direction decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content min-size max-size)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L272-L284)
---
## last-timeline-events
```clojure
(last-timeline-events client room-id decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content min-size max-size)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L286-L296)
---
## timeline-events-list
```clojure
(timeline-events-list client room-id start-from direction max-size min-size decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L298-L310)
---
## last-timeline-events-list
```clojure
(last-timeline-events-list client room-id max-size min-size decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L312-L322)
---
## timeline-events-around
```clojure
(timeline-events-around client room-id start-from max-size-before max-size-after decryption-timeout-ms fetch-timeout-ms fetch-size allow-replace-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L324-L335)
---
## timeline-event-relations
```clojure
(timeline-event-relations client room-id event-id relation-type)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L337-L338)
---
## user-all
```clojure
(user-all client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L340-L341)
---
## load-members
```clojure
(load-members client room-id wait on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L343-L344)
---
## user-by-id
```clojure
(user-by-id client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L346-L347)
---
## user-all-receipts
```clojure
(user-all-receipts client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L349-L350)
---
## user-receipts-by-id
```clojure
(user-receipts-by-id client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L352-L353)
---
## user-power-level
```clojure
(user-power-level client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L355-L356)
---
## can-kick-user
```clojure
(can-kick-user client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L358-L359)
---
## can-ban-user
```clojure
(can-ban-user client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L361-L362)
---
## can-unban-user
```clojure
(can-unban-user client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L364-L365)
---
## can-invite-user
```clojure
(can-invite-user client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L367-L368)
---
## can-invite
```clojure
(can-invite client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L370-L371)
---
## can-redact-event
```clojure
(can-redact-event client room-id event-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L373-L374)
---
## can-set-power-level-to-max
```clojure
(can-set-power-level-to-max client room-id user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L376-L377)
---
## can-send-event-by-class
```clojure
(can-send-event-by-class client room-id event-content-class)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L379-L380)
---
## can-send-event-by-content
```clojure
(can-send-event-by-content client room-id event-content)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L382-L383)
---
## user-presence
```clojure
(user-presence client user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L385-L386)
---
## user-account-data
```clojure
(user-account-data client event-content-class key)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L388-L389)
---
## user-direct-chats
```clojure
(user-direct-chats client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L391-L392)
---
## set-direct-chats
```clojure
(set-direct-chats client mappings on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L394-L395)
---
## notification-all
```clojure
(notification-all client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L397-L398)
---
## notification-all-flat
```clojure
(notification-all-flat client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L400-L401)
---
## notification-by-id
```clojure
(notification-by-id client id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L403-L404)
---
## notification-count
```clojure
(notification-count client)
(notification-count client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L406-L410)
---
## notification-unread
```clojure
(notification-unread client room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L412-L413)
---
## notification-mark-read
```clojure
(notification-mark-read client room-id event-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L415-L416)
---
## notification-mark-unread
```clojure
(notification-mark-unread client room-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L418-L419)
---
## notification-all-updates
```clojure
(notification-all-updates client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L421-L422)
---
## notification-dismiss
```clojure
(notification-dismiss client id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L424-L425)
---
## notification-dismiss-all
```clojure
(notification-dismiss-all client on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L427-L428)
---
## current-active-device-verification
```clojure
(current-active-device-verification client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L430-L431)
---
## active-device-verification-flow
```clojure
(active-device-verification-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L433-L434)
---
## current-active-user-verifications
```clojure
(current-active-user-verifications client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L436-L437)
---
## active-user-verifications-flow
```clojure
(active-user-verifications-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L439-L440)
---
## self-verification-methods
```clojure
(self-verification-methods client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L442-L443)
---
## start-device-verification
```clojure
(start-device-verification client user-id device-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L445-L446)
---
## start-user-verification
```clojure
(start-user-verification client user-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L448-L449)
---
## get-active-user-verification
```clojure
(get-active-user-verification client room-id event-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L451-L452)
---
## ready-verification
```clojure
(ready-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L454-L455)
---
## start-sas-verification
```clojure
(start-sas-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L457-L458)
---
## accept-sas-verification
```clojure
(accept-sas-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L460-L461)
---
## accept-verification
```clojure
(accept-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L463-L464)
---
## confirm-verification
```clojure
(confirm-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L466-L467)
---
## no-match-verification
```clojure
(no-match-verification client verification-id on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L469-L470)
---
## cancel-verification
```clojure
(cancel-verification client verification-id reason on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L472-L473)
---
## current-bootstrap-running
```clojure
(current-bootstrap-running client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L475-L476)
---
## bootstrap-running-flow
```clojure
(bootstrap-running-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L478-L479)
---
## bootstrap-cross-signing
```clojure
(bootstrap-cross-signing client opts on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L481-L482)
---
## bootstrap-cross-signing-from-passphrase
```clojure
(bootstrap-cross-signing-from-passphrase client passphrase opts on-success on-failure)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L484-L486)
---
## current-backup-version
```clojure
(current-backup-version client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L488-L489)
---
## backup-version-flow
```clojure
(backup-version-flow client)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L491-L492)
---
## device-trust-level
```clojure
(device-trust-level client user-id device-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L494-L495)
---
## timeline-trust-level
```clojure
(timeline-trust-level client room-id event-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L497-L498)
---
## user-trust-level
```clojure
(user-trust-level client user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L500-L501)
---
## device-keys-flow
```clojure
(device-keys-flow client user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L503-L504)
---
## cross-signing-keys-flow
```clojure
(cross-signing-keys-flow client user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal/bridge.clj#L506-L507)
## ol.trixnity.internal
# ol.trixnity.internal
## observe-flow
```clojure
(observe-flow client kotlin-flow)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal.clj#L17-L21)
---
## observe-keyed-flow-map
```clojure
(observe-keyed-flow-map client kotlin-outer-flow)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal.clj#L23-L40)
---
## observe-flow-list
```clojure
(observe-flow-list client kotlin-outer-flow)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal.clj#L42-L47)
---
## suspend-task
```clojure
(suspend-task bridge-fn & args)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal.clj#L49-L52)
---
## duration->millis
```clojure
(duration->millis duration)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/internal.clj#L54-L55)
## ol.trixnity.key
# ol.trixnity.key
Encryption-key state, backup state, and trust-level queries.
## Upstream Mapping
This namespace maps to Trixnity’s `KeyService` and `KeyBackupService`.
The public wrappers here cover:
* current and flow-based backup bootstrap state
* cross-signing bootstrap
* current and flow-based backup-version observation
* trust-level queries for users, devices, and timeline events
* device-key and cross-signing-key lookup
Use [`ol.trixnity.verification`](api/ol-trixnity-verification.adoc) for active verification workflows and
[`ol.trixnity.room`](api/ol-trixnity-room.adoc) when you need room timeline events to pair with
trust-level checks.
## current-bootstrap-running
```clojure
(current-bootstrap-running client)
```
Returns whether cross-signing bootstrap is currently running.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L28-L31)
---
## bootstrap-running
```clojure
(bootstrap-running client)
```
Returns a Missionary flow of cross-signing bootstrap state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L33-L37)
---
## bootstrap-cross-signing!
```clojure
(bootstrap-cross-signing! client)
(bootstrap-cross-signing! client opts)
```
Bootstraps cross-signing for the current Matrix account.
Returns a Missionary task. The task resolves to a normalized map with:
* `::mx/kind` — `"success"`, `"uia-required"`, or `"uia-error"`
* `::mx/recovery-key` — the recovery key generated by Trixnity
* `::mx/uia` — a UIA snapshot with completed stages, flows, session, and error details
By default this calls Trixnity’s `KeyService.bootstrapCrossSigning()`. If
`opts` includes `::mx/password`, the bridge attempts to complete a password
UIA step before returning. `::mx/user-id` can override the password UIA user
identifier; otherwise the current client’s Matrix user id is used.
`"uia-required"` and `"uia-error"` results do not expose Trixnity UIA
objects. Inspect `::mx/uia` to decide which public UIA flow or error remains.
Persist or display `::mx/recovery-key` securely; it is the user’s recovery
key material for the new bootstrap.
Be aware that upstream cross-signing bootstrap can replace existing
cross-signing and key-backup state for the account.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L39-L65)
---
## bootstrap-cross-signing-from-passphrase!
```clojure
(bootstrap-cross-signing-from-passphrase! client passphrase)
(bootstrap-cross-signing-from-passphrase! client passphrase opts)
```
Bootstraps cross-signing with a secret-storage passphrase.
Returns the same normalized Missionary task result as
[`bootstrap-cross-signing!`](#bootstrap-cross-signing!), but calls Trixnity’s
`KeyService.bootstrapCrossSigningFromPassphrase(passphrase)`.
`passphrase` configures the generated secret storage; use `::mx/password` in
`opts` for password-based UIA completion.
Be aware that upstream cross-signing bootstrap can replace existing
cross-signing and key-backup state for the account.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L67-L87)
---
## current-backup-version
```clojure
(current-backup-version client)
```
Returns the current active room-key-backup version, or nil when unavailable.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L89-L92)
---
## backup-version
```clojure
(backup-version client)
```
Returns a Missionary flow of the active room-key-backup version.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L94-L98)
---
## get-trust-level
```clojure
(get-trust-level client user-id)
(get-trust-level client user-id-or-room-id device-id-or-event-id)
```
Returns a Missionary flow of trust information.
With two arguments, `user-id` resolves to the trust level of that user.
With three arguments, the meaning depends on the first id:
* a `@user:server` value resolves device trust for `device-id`
* a `!room:server` value resolves the trust level for the device that sent
the timeline `event-id`
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L100-L122)
---
## get-device-keys
```clojure
(get-device-keys client user-id)
```
Returns a Missionary flow of device keys for `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L124-L128)
---
## get-cross-signing-keys
```clojure
(get-cross-signing-keys client user-id)
```
Returns a Missionary flow of cross-signing keys for `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/key.clj#L130-L134)
## ol.trixnity.media
# ol.trixnity.media
Generic media upload and download helpers built on top of Trixnity’s media
service.
Uploads happen in two steps:
* [`prepare-upload`](#prepare-upload) stages local bytes in Trixnity’s media store and returns
a prepared upload map containing an `upload://...` cache URI
* [`upload`](#upload) uploads either a prepared upload map or a local path-like source
and returns a handle exposing upload progress plus the final uploaded media
* [`get-media`](#get-media), [`get-encrypted-media`](#get-encrypted-media), and [`get-thumbnail`](#get-thumbnail) download media
as normalized handle maps carrying a JVM `InputStream`
Download selection follows the normalized event data:
* use [`get-media`](#get-media) for plain `::mx/url` attachment references
* use [`get-encrypted-media`](#get-encrypted-media) for `::mx/encrypted-file` or
`::mx/thumbnail-encrypted-file`
* use [`get-media`](#get-media) or [`get-encrypted-media`](#get-encrypted-media) for event-provided thumbnail
references already present on the event
* use [`get-thumbnail`](#get-thumbnail) only when asking the homeserver to generate a new
thumbnail for an MXC URI at explicit dimensions
* use [`temporary-file`](#temporary-file) only when a filesystem path is required for an
existing media handle
The public shapes here stay normalized as namespaced keyword maps so callers
do not need to work with Kotlin media APIs directly.
Resolved download handles all share the same shape:
```clojure
{::mx/input-stream
::mx/raw }
```
`::mx/input-stream` is the supported happy path. `::mx/raw` exists only so
[`temporary-file`](#temporary-file) can compose on top of an already fetched handle.
## ->TemporaryMediaFile
```clojure
(->TemporaryMediaFile path raw)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L147-L150)
---
## TemporaryMediaFile
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L147-L150)
---
## map->TemporaryMediaFile
```clojure
(map->TemporaryMediaFile m)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L147-L150)
---
## prepare-upload
```clojure
(prepare-upload client source)
(prepare-upload client source opts)
```
Stages a local media source in Trixnity’s media store and returns a
Missionary task of a prepared upload map.
`source` may be a string path, `java.io.File`, or `java.nio.file.Path`.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/file-name` | Optional logical file name stored in the returned metadata |
| `::mx/mime-type` | Optional MIME type forwarded to upstream media upload preparation |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L165-L190)
---
## upload
```clojure
(upload client source)
(upload client source opts)
```
Uploads a prepared upload map or local media source and returns a handle map.
When `source` is path-like, this first stages the file through
[`prepare-upload`](#prepare-upload) and then uploads the resulting cache URI.
Return value:
* `::mx/result` is a Missionary task that resolves to the uploaded media map
* `::mx/progress` is a Missionary flow of normalized progress snapshots with
`::mx/transferred` and optional `::mx/total`
This intentionally replaces the previous task-only return contract so one
`upload` call can drive both progress observation and the final upload result
from the same underlying operation.
Supported opts:
| key | description
|-----|-------------
| `::mx/file-name` | Optional logical file name when `source` is path-like |
| `::mx/mime-type` | Optional MIME type when `source` is path-like |
| `::mx/keep-in-cache` | Keep the staged media in the local cache after upload, defaults to `true` |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L192-L250)
---
## get-media
```clojure
(get-media client uri)
```
Downloads plain media identified by `uri`.
Returns a Missionary task of a normalized media handle:
* `::mx/input-stream`, the primary public readable stream
* `::mx/raw`, an opaque upstream media value used only for composition with
[`temporary-file`](#temporary-file)
`uri` should be an MXC URI such as `mxc://example.org/abc`.
Use this for event fields that already carry a plain media reference, such as
`::mx/url` or `::mx/thumbnail-url`.
Example:
```clojure
(m/sp
(let [handle (m/? (get-media client (::mx/url ev)))]
(slurp (::mx/input-stream handle))))
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L252-L280)
---
## get-encrypted-media
```clojure
(get-encrypted-media client encrypted-file)
```
Downloads encrypted media from normalized `encrypted-file` metadata.
`encrypted-file` must be the normalized encrypted-file map exposed on events
under `::mx/encrypted-file` or `::mx/thumbnail-encrypted-file`.
Returns the same normalized media-handle shape as [`get-media`](#get-media), with
`::mx/input-stream` as the public happy path and `::mx/raw` kept opaque for
[`temporary-file`](#temporary-file) composition.
Example:
```clojure
(m/sp
(let [handle (m/? (get-encrypted-media client
(::mx/encrypted-file ev)))]
(slurp (::mx/input-stream handle))))
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L282-L307)
---
## get-thumbnail
```clojure
(get-thumbnail client uri width height)
(get-thumbnail client uri width height opts)
```
Downloads a homeserver-generated thumbnail for `uri`.
This mirrors upstream Trixnity `getThumbnail`: it asks the homeserver to
generate a thumbnail for an MXC URI using explicit dimensions. It does not
read event-provided thumbnail references already present on an event. For
those, use [`get-media`](#get-media) or [`get-encrypted-media`](#get-encrypted-media) with
`::mx/thumbnail-url` or `::mx/thumbnail-encrypted-file`.
Returns the same normalized media-handle shape as [`get-media`](#get-media).
Supported opts:
| key | description
|-----|-------------
| `::mx/method` | Thumbnail resize method, either `:crop` or `:scale` |
| `::mx/animated` | Request animated thumbnails when upstream supports them |
Example:
```clojure
(m/sp
(let [handle (m/? (get-thumbnail client
(::mx/url ev)
320
200
{::mx/method :scale}))]
(slurp (::mx/input-stream handle))))
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L309-L354)
---
## temporary-file
```clojure
(temporary-file media)
```
Creates a temporary file from `media`.
`media` may be either a resolved media handle or a media-handle task returned
by [`get-media`](#get-media), [`get-encrypted-media`](#get-encrypted-media), or [`get-thumbnail`](#get-thumbnail).
Returns a Missionary task of a closeable [`TemporaryMediaFile`](#temporarymediafile) record. The
record exposes `:path` directly as a slurpable string path and should usually
be used with `with-open`.
Treat `::mx/raw` on media handles as opaque; it exists only so this helper
can compose with already-fetched media.
This is an opt-in JVM convenience for integrations that need a filesystem
path. If the underlying media handle cannot produce a temporary file, the task
fails with an exception from the bridge layer.
Example:
```clojure
(m/sp
(with-open [tmp (m/? (temporary-file
(get-media client (::mx/url ev))))]
(slurp (:path tmp))))
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/media.clj#L356-L395)
## ol.trixnity.notification
# ol.trixnity.notification
Notification snapshots and notification update flows.
## Upstream Mapping
This namespace maps to Trixnity’s `NotificationService`.
The public wrappers here cover:
* observing the full notification set in keyed or flat shapes
* looking up individual notifications and unread counts
* consuming live notification updates
Use [`ol.trixnity.room`](api/ol-trixnity-room.adoc) for room timeline access and
[`ol.trixnity.client`](api/ol-trixnity-client.adoc) for client lifecycle.
## get-all
```clojure
(get-all client)
```
Returns a Missionary flow of the current notifications as a list of inner flows.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L23-L26)
---
## get-all-flat
```clojure
(get-all-flat client)
```
Returns a Missionary flow of flattened notification snapshots.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L28-L31)
---
## get-by-id
```clojure
(get-by-id client id)
```
Returns a Missionary flow of the notification with `id`, or nil when unavailable.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L33-L37)
---
## get-count
```clojure
(get-count client)
(get-count client room-id)
```
Returns a Missionary flow of notification counts.
With one argument, returns the total count across all rooms.
With `room-id`, returns the count for that room.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L39-L48)
---
## is-unread
```clojure
(is-unread client room-id)
```
Returns a Missionary flow that is true when `room-id` is considered unread.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L50-L54)
---
## mark-read
```clojure
(mark-read client room-id event-id)
```
Marks `room-id` as read through `event-id` and returns a Missionary task.
This advances both the room’s read markers and clears explicit unread state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L56-L66)
---
## mark-unread
```clojure
(mark-unread client room-id)
```
Marks `room-id` as unread and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L68-L74)
---
## dismiss
```clojure
(dismiss client id)
```
Marks the notification with `id` as dismissed and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L76-L80)
---
## dismiss-all
```clojure
(dismiss-all client)
```
Dismisses all notifications and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L82-L85)
---
## get-all-updates
```clojure
(get-all-updates client)
```
Returns a Missionary flow of notification updates.
Upstream notes that this stream should not be buffered because consumed
updates are removed from the backing store as new values are requested.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/notification.clj#L87-L93)
## ol.trixnity.repo.common
# ol.trixnity.repo.common
## db
```clojure
(db handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L51-L53)
---
## json
```clojure
(json handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L55-L57)
---
## writer-pool
```clojure
(writer-pool handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L59-L61)
---
## reader-pool
```clojure
(reader-pool handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L63-L65)
---
## borrow-reader-conn!
```clojure
(borrow-reader-conn! handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L67-L69)
---
## borrow-writer-conn!
```clojure
(borrow-writer-conn! handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L71-L73)
---
## release-reader-conn!
```clojure
(release-reader-conn! handle conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L75-L78)
---
## release-writer-conn!
```clojure
(release-writer-conn! handle conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L80-L83)
---
## begin-deferred!
```clojure
(begin-deferred! conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L85-L88)
---
## begin-immediate!
```clojure
(begin-immediate! conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L90-L93)
---
## commit!
```clojure
(commit! conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L95-L98)
---
## rollback!
```clojure
(rollback! conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L100-L103)
---
## current-read-conn
```clojure
(current-read-conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L105-L107)
---
## current-write-conn
```clojure
(current-write-conn)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L109-L111)
---
## q-read
```clojure
(q-read handle query)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L113-L118)
---
## q-write
```clojure
(q-write handle query)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L120-L124)
---
## open!
```clojure
(open! path json)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L126-L128)
---
## ensure-schema!
```clojure
(ensure-schema! handle)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L130-L134)
---
## encode-json
```clojure
(encode-json json serializer value)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L136-L138)
---
## decode-json
```clojure
(decode-json json serializer value)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L140-L142)
---
## first-row
```clojure
(first-row rows)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L144-L147)
---
## maybe-rows
```clojure
(maybe-rows rows)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L149-L151)
---
## in-placeholders
```clojure
(in-placeholders n)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L153-L155)
---
## minimal-get
```clojure
(minimal-get handle table repo-key serializer)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L157-L163)
---
## minimal-save!
```clojure
(minimal-save! handle table repo-key payload)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L165-L172)
---
## minimal-delete!
```clojure
(minimal-delete! handle table repo-key)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L174-L179)
---
## delete-all!
```clojure
(delete-all! handle table)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L181-L185)
---
## full-get-all
```clojure
(full-get-all handle table serializer)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L187-L192)
---
## room-id-str
```clojure
(room-id-str room-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L194-L196)
---
## user-id-str
```clojure
(user-id-str user-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L198-L200)
---
## event-id-str
```clojure
(event-id-str event-id)
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo/common.clj#L202-L204)
## ol.trixnity.repo
# ol.trixnity.repo
Built-in repository helpers for the sqlite4clj-backed repository setup.
Most callers do not need to interact with the raw repository handle or the
Kotlin bridge types directly. Use [`sqlite4clj-config`](#sqlite4clj-config) with
[`ol.trixnity.client/open`](api/ol-trixnity-client.adoc#open).
Advanced callers can still build a `MatrixClient` with some other repository
implementation and pass that client to [`ol.trixnity.client/open`](api/ol-trixnity-client.adoc#open) via
`::mx/client`.
## sqlite4clj-config
```clojure
(sqlite4clj-config options)
```
Returns config entries for the built-in sqlite4clj repository and okio media store.
Prefer the namespaced keys from [`ol.trixnity.schemas`](api/ol-trixnity-schemas.adoc). Plain keywords are
still accepted here as a convenience when normalizing app config.
Options:
| | |
| --- | --- |
| key | description |
| `::mx/database-path` | SQLite file path used by `ol.trixnity.repo` |
| `::mx/media-path` | Directory used by the okio-backed media store |
Example:
```clojure
(m/? (client/open
(merge
{::mx/homeserver-url "https://matrix.example.org"
::mx/user-id "@bot:example.org"
::mx/password "secret"}
(repo/sqlite4clj-config
{::mx/database-path "./var/trixnity.sqlite"
::mx/media-path "./var/media"}))))
```
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo.clj#L51-L78)
---
## open-handle!
```clojure
(open-handle! path json)
```
Opens a low-level sqlite4clj repository handle for `path`.
Most callers should prefer [`sqlite4clj-config`](#sqlite4clj-config) with
[`ol.trixnity.client/open`](api/ol-trixnity-client.adoc#open). This is the lower-level escape hatch used by the
built-in repository assembly.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo.clj#L80-L87)
---
## create-repositories
```clojure
(create-repositories handle mappings)
```
Builds the repository map expected by the Kotlin bridge from `handle`.
This is primarily a low-level integration seam for custom client assembly.
Most callers should use [`sqlite4clj-config`](#sqlite4clj-config) instead of constructing this
map directly.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo.clj#L898-L937)
## ol.trixnity.room.message
# ol.trixnity.room.message
Helpers for constructing normalized room message payloads.
This namespace provides small builders for the message-spec maps accepted by
[`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
The helpers here cover the common message-spec cases:
* [`text`](#text) builds a normalized text message payload
* [`emote`](#emote) builds a normalized emote message payload
* [`audio`](#audio), [`image`](#image), and [`file`](#file) build normalized attachment payloads
* [`reply-to`](#reply-to) attaches reply metadata from a normalized event map
Use [`ol.trixnity.event`](api/ol-trixnity-event.adoc) to inspect the events you are replying to.
## text
```clojure
(text body)
(text body opts)
```
Builds a text message-spec map understood by [`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L54-L64)
---
## emote
```clojure
(emote body)
(emote body opts)
```
Builds an emote message-spec map understood by [`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L66-L76)
---
## audio
```clojure
(audio source-path)
(audio source-path opts)
```
Builds an audio message-spec map understood by [`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L78-L83)
---
## image
```clojure
(image source-path)
(image source-path opts)
```
Builds an image message-spec map understood by [`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L85-L90)
---
## file
```clojure
(file source-path)
(file source-path opts)
```
Builds a file message-spec map understood by [`ol.trixnity.room/send-message`](api/ol-trixnity-room.adoc#send-message).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L92-L97)
---
## reply-to
```clojure
(reply-to message ev)
```
Associates reply metadata from normalized event `ev` onto `message`.
When `ev` already carries relation metadata, that relation is copied into the
reply target so upstream threading semantics are preserved.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room/message.clj#L99-L111)
## ol.trixnity.room
# ol.trixnity.room
Room operations, state queries, and timeline traversal.
## Upstream Mapping
This namespace maps primarily to Trixnity’s `RoomService` on
`de.connect2x.trixnity.client.MatrixClient`.
The public wrappers here cover three upstream groupings:
* room mutations such as room creation, invites, messages, reactions, and
supported room state events and redactions
* room observation and state operations such as `getById`, `getAll`,
`getAccountData`, `getState`, and `getOutbox`
* room-scoped timeline lookup and traversal helpers exposed through the
room service and its timeline helpers
Use [`ol.trixnity.user`](api/ol-trixnity-user.adoc) for `UserService` APIs and
[`ol.trixnity.notification`](api/ol-trixnity-notification.adoc), [`ol.trixnity.verification`](api/ol-trixnity-verification.adoc), and
[`ol.trixnity.key`](api/ol-trixnity-key.adoc) for the other non-room service mappings.
## get-outbox
===
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L30-L30)
===
```clojure
(get-outbox client)
(get-outbox client room-id)
(get-outbox client room-id transaction-id)
```
Returns Missionary flows over room outbox state.
Arities:
* `(get-outbox client)` returns all outbox entries as a list of inner flows
* `(get-outbox client room-id)` scopes that list to one room
* `(get-outbox client room-id transaction-id)` returns the single outbox
entry flow for that transaction id, including attachment upload progress
under `::mx/media-upload-progress` when available
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L487-L505)
---
## create-room
```clojure
(create-room client opts)
```
Creates a room and returns a Missionary task that resolves to the new room id.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/room-name` | Optional room display name. |
| `::mx/topic` | Optional room topic. |
| `::mx/invite` | Optional vector of Matrix user ids to invite during creation. |
| `::mx/preset` | Optional preset keyword, one of `:private-chat`, `:public-chat`, or `:trusted-private-chat`. |
| `::mx/is-direct` | Optional direct-message flag for invite membership events. |
| `::mx/visibility` | Optional room-directory visibility, either `:private` or `:public`. |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L64-L81)
---
## invite-user
```clojure
(invite-user client room-id user-id)
(invite-user client room-id user-id opts)
```
Invites `user-id` to `room-id` and returns a Missionary task.
Supported opts:
| key | description
|-----|-------------
| `::mx/timeout` | Maximum time to wait for the invite request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L83-L101)
---
## kick-user
```clojure
(kick-user client room-id user-id)
(kick-user client room-id user-id opts)
```
Kicks `user-id` from `room-id` and returns a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/reason` | Optional Matrix kick reason passed through to Trixnity |
| `::mx/timeout` | Maximum time to wait for the kick request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L103-L123)
---
## ban-user
```clojure
(ban-user client room-id user-id)
(ban-user client room-id user-id opts)
```
Bans `user-id` from `room-id` and returns a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/reason` | Optional Matrix ban reason passed through to Trixnity |
| `::mx/timeout` | Maximum time to wait for the ban request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L125-L145)
---
## unban-user
```clojure
(unban-user client room-id user-id)
(unban-user client room-id user-id opts)
```
Unbans `user-id` from `room-id` and returns a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/reason` | Optional Matrix unban reason passed through to Trixnity |
| `::mx/timeout` | Maximum time to wait for the unban request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L147-L167)
---
## join-room
```clojure
(join-room client room-id-or-alias)
(join-room client room-id-or-alias opts)
```
Joins `room-id-or-alias` and returns a Missionary task.
Supported opts:
| key | description
|-----|-------------
| `::mx/timeout` | Maximum time to wait for the join request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L169-L185)
---
## leave-room
```clojure
(leave-room client room-id)
(leave-room client room-id opts)
```
Leaves `room-id` and returns a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/reason` | Optional Matrix leave reason passed through to Trixnity |
| `::mx/timeout` | Maximum time to wait for the leave request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L187-L205)
---
## forget-room
```clojure
(forget-room client room-id)
(forget-room client room-id opts)
```
Forgets `room-id` locally and returns a Missionary task.
Upstream notes that this is intended for rooms in `LEAVE` membership.
Supported opts:
| key | description
|-----|-------------
| `::mx/force` | Force forgetting even when the usual upstream preconditions are not met |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L207-L225)
---
## send-message
```clojure
(send-message client room-id message)
(send-message client room-id message opts)
```
Queues `message` for `room-id` and returns a Missionary task of a send handle.
The task resolves to:
* `::mx/transaction-id` for the queued outbox entry
* `::mx/status`, a Missionary flow backed by [`get-outbox`](#get-outbox) for that
transaction id
The status flow may emit `nil` after the message leaves the outbox, which
usually means it was echoed back by sync, cancelled, or otherwise removed.
Supported opts:
| key | description
|-----|-------------
| `::mx/timeout` | Maximum time to wait for the send operation |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L227-L257)
---
## send-reaction
```clojure
(send-reaction client room-id ev key)
(send-reaction client room-id ev key opts)
```
Sends a reaction to event `ev` in `room-id` and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L259-L273)
---
## send-state-event
```clojure
(send-state-event client room-id state-event)
(send-state-event client room-id state-event opts)
```
Sends a supported room state event map to `room-id`.
Supported state-event payloads:
* `{::mx/type "m.room.name", ::mx/name ...}`
* `{::mx/type "m.room.topic", ::mx/topic ...}`
* `{::mx/type "m.room.avatar", ::mx/url ...}`
* `{::mx/type "m.room.power_levels", ...}` using the same content keys as
[`set-power-levels`](#set-power-levels)
* `{::mx/type "m.space.child", ::mx/state-key child-room-id, ::mx/via ...}`
* `{::mx/type "m.space.parent", ::mx/state-key parent-space-id, ::mx/via ...}`
`::mx/state-key` is optional for room state events that use the empty string
state key. Space relation events require a room id state key.
Supported opts:
| key | description
|-----|-------------
| `::mx/timeout` | Maximum time to wait for the send operation |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L275-L306)
---
## set-power-levels
```clojure
(set-power-levels client room-id power-levels)
(set-power-levels client room-id power-levels opts)
```
Sets the `m.room.power_levels` content for `room-id`.
`power-levels` is a map with Matrix power-level fields using namespaced
Clojure keys:
| | |
| --- | --- |
| key | description |
| `::mx/ban-level` | Level required to ban users |
| `::mx/event-levels` | Map of event type string to required level |
| `::mx/events-default-level` | Default level for message events |
| `::mx/invite-level` | Level required to invite users |
| `::mx/kick-level` | Level required to kick users |
| `::mx/redact-level` | Level required to redact others' events |
| `::mx/state-default-level` | Default level for state events |
| `::mx/user-levels` | Map of user id string to user level |
| `::mx/users-default-level` | Default level for users not in `::mx/user-levels` |
| `::mx/notification-levels` | Map of notification key to required level |
| `::mx/external-url` | Optional external URL attached to the state event content |
Supported opts:
| key | description
|-----|-------------
| `::mx/timeout` | Maximum time to wait for the send operation |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L308-L343)
---
## redact-event
```clojure
(redact-event client room-id event-id)
(redact-event client room-id event-id opts)
```
Redacts `event-id` in `room-id` and returns a Missionary task of the redaction event id.
Supported opts:
| key | description
|-----|-------------
| `::mx/reason` | Optional redaction reason sent to the homeserver |
| `::mx/timeout` | Maximum time to wait for the redact operation |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L345-L365)
---
## cancel-send-message
```clojure
(cancel-send-message client room-id transaction-id)
```
Cancels an outbox message identified by `transaction-id` and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L367-L375)
---
## retry-send-message
```clojure
(retry-send-message client room-id transaction-id)
```
Retries an outbox message identified by `transaction-id` and returns a Missionary task.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L377-L385)
---
## get-by-id
```clojure
(get-by-id client room-id)
```
Returns a Missionary flow of the room for `room-id`, or nil when unavailable.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L387-L391)
---
## get-all
```clojure
(get-all client)
```
Returns a Missionary flow of room flows keyed by room id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L393-L396)
---
## get-all-flat
```clojure
(get-all-flat client)
```
Returns a Missionary flow of flattened room snapshots.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L398-L401)
---
## current-users-typing
```clojure
(current-users-typing client)
```
Returns the current typing-state snapshot keyed by room id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L403-L406)
---
## users-typing
```clojure
(users-typing client)
```
Returns a relieved Missionary flow of typing-state snapshots keyed by room id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L408-L412)
---
## set-typing
```clojure
(set-typing client room-id typing?)
(set-typing client room-id typing? opts)
```
Sets the typing status for `room-id` and returns a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/timeout` | How long the typing notification should remain active |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L414-L432)
---
## get-power-levels
```clojure
(get-power-levels client room-id)
```
Returns a Missionary flow of normalized `m.room.power_levels` content.
The flow emits `nil` when no local power-level state is available for
`room-id`. Emitted maps use the same content keys accepted by
[`set-power-levels`](#set-power-levels).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L434-L442)
---
## get-account-data
```clojure
(get-account-data client room-id event-content-class)
(get-account-data client room-id event-content-class key)
```
Returns a Missionary flow of room account-data content.
When `key` is omitted, the empty-string key is used.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L444-L458)
---
## get-state
```clojure
(get-state client room-id event-content-class)
(get-state client room-id event-content-class state-key)
```
Returns a Missionary flow of room state for `event-content-class`.
When `state-key` is omitted, the empty-string state key is used.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L460-L474)
---
## get-all-state
```clojure
(get-all-state client room-id event-content-class)
```
Returns a Missionary flow of state-event flows keyed by state key.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L476-L485)
---
## get-outbox-flat
```clojure
(get-outbox-flat client)
(get-outbox-flat client room-id)
```
Returns flattened Missionary flows over room outbox state.
With `room-id`, scopes the flattened outbox view to a single room.
Flattened outbox snapshots also include `::mx/media-upload-progress` when an
attachment-backed send is still uploading.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L507-L518)
---
## fill-timeline-gaps
```clojure
(fill-timeline-gaps client room-id event-id)
(fill-timeline-gaps client room-id event-id opts)
```
Fills timeline gaps around `event-id` in `room-id` and returns a Missionary task.
Supported opts:
| key | description
|-----|-------------
| `::mx/limit` | Maximum number of events to request while filling gaps (default `20`) |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L520-L538)
---
## get-timeline-event
```clojure
(get-timeline-event client room-id event-id)
(get-timeline-event client room-id event-id opts)
```
Returns a Missionary flow of the timeline event for `event-id`.
Upstream notes that this lookup may traverse locally stored events and fill
remote gaps when the event is not available locally.
Supported opts:
| key | description
|-----|-------------
| `::mx/decryption-timeout` | Timeout used while decrypting timeline events
| `::mx/fetch-timeout` | Timeout for remote fetches when the event is missing locally
| `::mx/fetch-size` | Maximum number of events fetched from the server at once
| `::mx/allow-replace-content` | Replace event content when an `m.replace` relation is present |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L540-L570)
---
## get-previous-timeline-event
```clojure
(get-previous-timeline-event client timeline-event)
(get-previous-timeline-event client timeline-event opts)
```
Returns a Missionary flow of the previous timeline event relative to `timeline-event`.
Returns nil when upstream cannot traverse backward from the supplied event.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L572-L588)
---
## get-next-timeline-event
```clojure
(get-next-timeline-event client timeline-event)
(get-next-timeline-event client timeline-event opts)
```
Returns a Missionary flow of the next timeline event relative to `timeline-event`.
Returns nil when upstream cannot traverse forward from the supplied event.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L590-L606)
---
## get-last-timeline-event
```clojure
(get-last-timeline-event client room-id)
(get-last-timeline-event client room-id opts)
```
Returns a Missionary outer flow whose values are flows of the latest timeline event.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L608-L624)
---
## get-timeline-events
```clojure
(get-timeline-events client response)
(get-timeline-events client response opts)
(get-timeline-events client room-id start-from direction)
(get-timeline-events client room-id start-from direction opts)
```
Returns Missionary flows over timeline events.
Arities:
* `(get-timeline-events client response opts)` extracts timeline events from
a sync `response`
* `(get-timeline-events client room-id start-from direction opts)` traverses
a room timeline from `start-from` in `:backwards` or `:forwards`
The room traversal arity follows upstream behavior: it emits flows of events,
may fetch missing events from the server, and can be bounded with
`::mx/min-size` and `::mx/max-size`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L626-L669)
---
## get-last-timeline-events
```clojure
(get-last-timeline-events client room-id)
(get-last-timeline-events client room-id opts)
```
Returns a Missionary flow whose values are flows of flows for the latest timeline events.
This mirrors upstream’s nested-flow shape for continuously updated room-end
traversal.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L671-L692)
---
## get-timeline-events-list
```clojure
(get-timeline-events-list client room-id start-from direction max-size min-size)
(get-timeline-events-list client room-id start-from direction max-size min-size opts)
```
Returns a Missionary flow of timeline-event lists starting from `start-from`.
`max-size` and `min-size` bound the list-shaped traversal directly.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L694-L720)
---
## get-last-timeline-events-list
```clojure
(get-last-timeline-events-list client room-id max-size min-size)
(get-last-timeline-events-list client room-id max-size min-size opts)
```
Returns a Missionary flow of the latest timeline events as lists.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L722-L742)
---
## get-timeline-events-around
```clojure
(get-timeline-events-around client room-id start-from max-size-before max-size-after)
(get-timeline-events-around client room-id start-from max-size-before max-size-after opts)
```
Returns a Missionary flow of timeline-event lists centered around `start-from`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L744-L766)
---
## get-timeline-events-from-now-on
```clojure
(get-timeline-events-from-now-on client)
(get-timeline-events-from-now-on client opts)
```
Returns a Missionary flow of timeline events received after subscription starts.
Upstream notes that timeline gaps are not filled automatically for this live
stream.
Supported opts:
| key | description
|-----|-------------
| `::mx/decryption-timeout` | Timeout used while decrypting live events
| `::mx/sync-response-buffer-size` | Number of sync responses buffered while events are consumed |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L768-L789)
---
## get-timeline-event-relations
```clojure
(get-timeline-event-relations client room-id event-id relation-type)
```
Returns a Missionary flow of related timeline-event flows keyed by related event id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/room.clj#L791-L799)
## ol.trixnity.schemas
# ol.trixnity.schemas
Malli schema registry for ol.trixnity
Public namespaces validate arguments and normalize bridge-shaped data through
the schemas defined here before crossing into
[`ol.trixnity.internal.bridge`](api/ol-trixnity-internal-bridge.adoc).
The registry includes:
* public request and options maps for client, room, notification, and key
operations
* normalized event, profile, room, notification, verification, and trust
data shapes
* helper functions for building a registry and enforcing schema validation
Most callers use these namespaced keys indirectly through the higher-level
public APIs. Reach for this namespace directly when constructing config or
payload maps by hand.
## schemas
```clojure
(schemas _)
```
Returns the project Malli schema map for `opts`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/schemas.clj#L73-L894)
---
## registry
```clojure
(registry opts)
```
Builds a Malli registry containing the project schemas and default schemas.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/schemas.clj#L896-L901)
---
## schema-registry
Default Malli registry used by public API validation.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/schemas.clj#L903-L905)
---
## validate!
```clojure
(validate! schema-id data)
(validate! registry schema-id data)
```
Validates `data` against `schema-id` and returns `data` on success.
Throws `ExceptionInfo` with humanized Malli errors on validation failure.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/schemas.clj#L910-L924)
## ol.trixnity.space
# ol.trixnity.space
Matrix space creation, hierarchy, and relation helpers.
Spaces are Matrix rooms with room type `m.space`. This namespace keeps the
wrapper thin: optional values are passed only when callers provide them, and
parent/child relation state is managed explicitly. Use [`ol.trixnity.room`](api/ol-trixnity-room.adoc)
and [`ol.trixnity.user`](api/ol-trixnity-user.adoc) for generic room membership and power-level APIs.
## create-space
```clojure
(create-space client)
(create-space client opts)
```
Creates a Matrix space and returns a Missionary task resolving to its room id.
The request uses upstream create-room defaults and only forces the Matrix room
type to `m.space`. It does not add encryption or relation state.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/room-name` | Optional space name |
| `::mx/topic` | Optional space topic |
| `::mx/invite` | Optional users to invite during creation |
| `::mx/preset` | Optional upstream create-room preset |
| `::mx/is-direct` | Optional direct-room flag passed through upstream |
| `::mx/visibility` | Optional directory visibility |
| `::mx/power-levels` | Optional upstream power-level content override |
| `::mx/timeout` | Maximum time to wait for the create request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L37-L62)
---
## create-subspace
```clojure
(create-subspace client parent-space-id opts)
```
Creates a Matrix space and adds it as a child of `parent-space-id`.
This composes [`create-space`](#create-space) with [`set-child`](#set-child). It writes only the
`m.space.child` event in `parent-space-id`; use [`set-parent`](#set-parent) separately if
the child should also contain an `m.space.parent` event.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/room-name` | Optional subspace name |
| `::mx/topic` | Optional subspace topic |
| `::mx/invite` | Optional users to invite during creation |
| `::mx/preset` | Optional upstream create-room preset |
| `::mx/is-direct` | Optional direct-room flag passed through upstream |
| `::mx/visibility` | Optional directory visibility |
| `::mx/power-levels` | Optional upstream power-level content override |
| `::mx/via` | Required non-empty set of server names for the child relation |
| `::mx/order` | Optional Matrix space child ordering string |
| `::mx/suggested` | Optional suggested-child flag |
| `::mx/external-url` | Optional external URL for the child relation |
| `::mx/timeout` | Maximum time to wait for each request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L64-L101)
---
## hierarchy
```clojure
(hierarchy client space-id)
(hierarchy client space-id opts)
```
Returns one paginated hierarchy page for `space-id` as a Missionary task.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/from` | Optional pagination token |
| `::mx/limit` | Optional maximum rooms per page |
| `::mx/max-depth` | Optional maximum traversal depth |
| `::mx/suggested-only` | When true, include only suggested children |
| `::mx/timeout` | Maximum time to wait for the hierarchy request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L103-L124)
---
## get-all
```clojure
(get-all client)
```
Returns a Missionary flow of local space flows keyed by room id.
This observes spaces already known to the client. It does not perform public
room discovery.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L126-L132)
---
## get-all-flat
```clojure
(get-all-flat client)
```
Returns a Missionary flow of local flattened space snapshots.
This observes spaces already known to the client. It does not perform public
room discovery.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L134-L140)
---
## get-children
```clojure
(get-children client space-id)
```
Returns a Missionary flow of `m.space.child` state flows keyed by child room id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L142-L146)
---
## get-child
```clojure
(get-child client space-id child-room-id)
```
Returns a Missionary flow of the child relation for `child-room-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L148-L153)
---
## get-parents
```clojure
(get-parents client room-id)
```
Returns a Missionary flow of `m.space.parent` state flows keyed by parent space id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L155-L159)
---
## set-child
```clojure
(set-child client space-id child-room-id content)
(set-child client space-id child-room-id content opts)
```
Writes an `m.space.child` event in `space-id` for `child-room-id`.
The `content` map must include `::mx/via`. It may also include `::mx/order`,
`::mx/suggested`, and `::mx/external-url`.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/timeout` | Maximum time to wait for the state event request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L161-L184)
---
## remove-child
```clojure
(remove-child client space-id child-room-id)
(remove-child client space-id child-room-id opts)
```
Removes the child relation from `space-id` to `child-room-id`.
This sends empty `m.space.child` content for the child state key, which makes
the relation invalid under Matrix space rules.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/timeout` | Maximum time to wait for the state event request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L186-L207)
---
## set-parent
```clojure
(set-parent client room-id parent-space-id content)
(set-parent client room-id parent-space-id content opts)
```
Writes an `m.space.parent` event in `room-id` for `parent-space-id`.
The `content` map must include `::mx/via`. It may also include
`::mx/canonical` and `::mx/external-url`.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/timeout` | Maximum time to wait for the state event request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L209-L232)
---
## remove-parent
```clojure
(remove-parent client room-id parent-space-id)
(remove-parent client room-id parent-space-id opts)
```
Removes the parent relation from `room-id` to `parent-space-id`.
This sends empty `m.space.parent` content for the parent state key, which
makes the relation invalid under Matrix space rules.
Supported opts:
| | |
| --- | --- |
| key | description |
| `::mx/timeout` | Maximum time to wait for the state event request |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/space.clj#L234-L255)
## ol.trixnity.user
# ol.trixnity.user
Room user state, receipts, presence, and permission checks.
## Upstream Mapping
This namespace wraps the user-oriented room APIs exposed by Trixnity’s
`RoomService`.
The public wrappers here cover:
* room member lookup by id or as keyed flow maps
* per-user receipt and power-level observation
* permission checks for invites, bans, kicks, redactions, power changes, and
sending events
* user presence and global account-data queries
Use [`ol.trixnity.room`](api/ol-trixnity-room.adoc) for room lifecycle and timeline access, and
[`ol.trixnity.client`](api/ol-trixnity-client.adoc) for client-wide state.
## load-members
```clojure
(load-members client room-id)
(load-members client room-id opts)
```
Loads room members for `room-id` and returns a Missionary task.
Supported opts:
| key | description
|-----|-------------
| `::mx/wait` | When true, wait for the member load to finish before resolving the task (default `true`) |
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L26-L42)
---
## get-all
```clojure
(get-all client room-id)
```
Returns a Missionary flow of room members keyed by user id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L44-L48)
---
## get-by-id
```clojure
(get-by-id client room-id user-id)
```
Returns a Missionary flow of the room member for `user-id`, or nil when unavailable.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L50-L55)
---
## get-all-receipts
```clojure
(get-all-receipts client room-id)
```
Returns a Missionary flow of per-user receipt flows keyed by user id.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L57-L61)
---
## get-receipts-by-id
```clojure
(get-receipts-by-id client room-id user-id)
```
Returns a Missionary flow of receipts for `user-id` in `room-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L63-L68)
---
## get-power-level
```clojure
(get-power-level client room-id user-id)
```
Returns a Missionary flow of the current power-level view for `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L70-L75)
---
## can-kick-user
```clojure
(can-kick-user client room-id user-id)
```
Returns a Missionary flow that reports whether the current client can kick `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L77-L82)
---
## can-ban-user
```clojure
(can-ban-user client room-id user-id)
```
Returns a Missionary flow that reports whether the current client can ban `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L84-L89)
---
## can-unban-user
```clojure
(can-unban-user client room-id user-id)
```
Returns a Missionary flow that reports whether the current client can unban `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L91-L96)
---
## can-invite-user
```clojure
(can-invite-user client room-id user-id)
```
Returns a Missionary flow that reports whether the current client can invite `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L98-L103)
---
## can-invite
```clojure
(can-invite client room-id)
```
Returns a Missionary flow that reports whether the current client can invite users to `room-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L105-L109)
---
## can-redact-event
```clojure
(can-redact-event client room-id event-id)
```
Returns a Missionary flow that reports whether the current client can redact `event-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L111-L116)
---
## can-set-power-level-to-max
```clojure
(can-set-power-level-to-max client room-id user-id)
```
Returns a Missionary flow of the maximum power level the current client may assign to `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L118-L123)
---
## can-send-event
```clojure
(can-send-event client room-id event-class-or-content)
```
Returns a Missionary flow that reports whether the current client can send an event.
`event-class-or-content` may be either a room-event-content class or a
concrete room-event-content instance.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L125-L142)
---
## get-presence
```clojure
(get-presence client user-id)
```
Returns a Missionary flow of presence data for `user-id`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L144-L148)
---
## get-account-data
```clojure
(get-account-data client event-content-class)
(get-account-data client event-content-class key)
```
Returns a Missionary flow of global account-data content.
When `key` is omitted, the empty-string state key is used.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L150-L161)
---
## get-direct-chats
```clojure
(get-direct-chats client)
```
Returns a Missionary flow of the current `m.direct` mapping.
The emitted value is a map of Matrix user ids to sets of direct-room ids.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L163-L168)
---
## set-direct-chats
```clojure
(set-direct-chats client mappings)
```
Writes the `m.direct` mapping and returns a Missionary task.
`mappings` must be a map of Matrix user ids to sets of room ids.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/user.clj#L170-L178)
## ol.trixnity.verification
# ol.trixnity.verification
Verification state snapshots and active verification flows.
## Upstream Mapping
This namespace maps to Trixnity’s `VerificationService`.
The public wrappers here cover:
* creating device and user verification requests
* accepting requests, starting and accepting SAS, confirming or rejecting SAS,
and cancelling active verifications
* the current active device verification and its relieved flow
* the current active user verifications and their relieved flow
* self-verification method discovery
Use [`ol.trixnity.key`](api/ol-trixnity-key.adoc) for trust and key-management APIs that often
accompany verification workflows.
## current-active-device-verification
```clojure
(current-active-device-verification client)
```
Returns the current active device verification, or nil when none is active.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L30-L33)
---
## active-device-verification
```clojure
(active-device-verification client)
```
Returns a relieved Missionary flow of the current active device verification.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L35-L39)
---
## current-active-user-verifications
```clojure
(current-active-user-verifications client)
```
Returns the current active user verifications as a vector.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L41-L44)
---
## active-verification-snapshots
```clojure
(active-verification-snapshots client)
```
Returns active device and user verification snapshots as a vector.
Device verification, when present, appears first, followed by active user
verifications in Trixnity order. Each snapshot contains a stable
`::mx/verification-id` suitable for [`ready!`](#ready!), [`start-sas!`](#start-sas!),
[`accept-sas!`](#accept-sas!), [`confirm!`](#confirm!), [`no-match!`](#no-match!), and [`cancel!`](#cancel!).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L46-L57)
---
## status
```clojure
(status client)
```
Returns active verification snapshots.
Alias for [`active-verification-snapshots`](#active-verification-snapshots).
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L59-L64)
---
## active-user-verifications
```clojure
(active-user-verifications client)
```
Returns a relieved Missionary flow of the current active user verifications.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L66-L70)
---
## start-device-verification!
```clojure
(start-device-verification! client user-id device-id)
```
Creates a device verification request and returns a Missionary task.
`user-id` is the target Matrix user id. `device-id` is the target Matrix
device id. The task resolves to a normalized active-verification snapshot.
Maps to Trixnity `VerificationService.createDeviceVerificationRequest`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L72-L85)
---
## start-user-verification!
```clojure
(start-user-verification! client user-id)
```
Creates a user verification request and returns a Missionary task.
The task resolves to a normalized active-verification snapshot. Maps to
Trixnity `VerificationService.createUserVerificationRequest`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L87-L94)
---
## get-active-user-verification!
```clojure
(get-active-user-verification! client room-id event-id)
```
Gets or creates an active user verification for a room request event.
`room-id` and `event-id` identify a Matrix verification request event. The
task resolves to a normalized active-verification snapshot or nil. Maps to
Trixnity `VerificationService.getActiveUserVerification`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L96-L108)
---
## ready!
```clojure
(ready! client verification-id)
```
Sends `m.key.verification.ready` for an incoming verification request.
`verification-id` must come from an active-verification snapshot. The task
resolves to the updated snapshot. Maps to Trixnity
`ActiveVerificationState.TheirRequest.ready`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L110-L118)
---
## start-sas!
```clojure
(start-sas! client verification-id)
```
Starts SAS verification for a ready verification request.
`verification-id` must come from an active-verification snapshot. The task
resolves to the updated snapshot. Maps to Trixnity
`ActiveVerificationState.Ready.start(VerificationMethod.Sas)`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L120-L128)
---
## accept-sas!
```clojure
(accept-sas! client verification-id)
```
Accepts an incoming SAS start event.
`verification-id` must come from an active-verification snapshot. The task
resolves to the updated snapshot. Maps to Trixnity
`ActiveSasVerificationState.TheirSasStart.accept`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L130-L138)
---
## accept!
```clojure
(accept! client verification-id)
```
Accepts the current incoming verification step.
`verification-id` must come from an active-verification snapshot. This maps
to Trixnity’s available incoming action for the current state: request
`ready` or SAS `accept`. Prefer [`ready!`](#ready!) or [`accept-sas!`](#accept-sas!) when the
caller already knows the state.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L140-L149)
---
## confirm!
```clojure
(confirm! client verification-id)
```
Confirms that the SAS comparison matches.
`verification-id` must come from an active-verification snapshot. The task
resolves to the updated snapshot. Maps to Trixnity
`ActiveSasVerificationState.ComparisonByUser.match`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L151-L159)
---
## no-match!
```clojure
(no-match! client verification-id)
```
Rejects the SAS comparison as a mismatch.
`verification-id` must come from an active-verification snapshot. The task
resolves to the updated snapshot. Maps to Trixnity
`ActiveSasVerificationState.ComparisonByUser.noMatch`.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L161-L169)
---
## cancel!
```clojure
(cancel! client verification-id)
(cancel! client verification-id reason)
```
Cancels an active verification and returns a Missionary task.
`verification-id` must come from an active-verification snapshot. With
`reason`, passes that Matrix cancellation reason through to Trixnity.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L171-L185)
---
## get-self-verification-methods
```clojure
(get-self-verification-methods client)
```
Returns a Missionary flow of available self-verification methods.
Upstream models this as a state machine that distinguishes unmet
preconditions, no-cross-signing-yet, already-cross-signed, and available
self-verification methods.
[source,window=_blank](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/verification.clj#L187-L194)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## ++[++UNRELEASED++]++
### Changed
* Add public room power-level helpers for reading and writing `m.room.power_levels`.
* Add public Matrix space helpers for space creation, local space listing, hierarchy inspection, and relation management.
* Add public Matrix verification workflow helpers and normalized active-verification snapshots.
* Add public cross-signing bootstrap helpers under `ol.trixnity.key`, including password UIA completion and passphrase-based secret-storage bootstrap.
* Change git dependency prep to compile classes without running tests, so Java 21 consumers can prepare the library from a git SHA.
* Upgrade Trixnity to 5.6.0, including Matrix 1.18 support and upstream profile/cache fixes.
## `v0.0.2` (2026-05-17)
### Added
* Add sqlite4clj-backed sticky event repository support required by Trixnity 5.5.x.
### Changed
* Upgrade Trixnity to 5.5.2, Ktor to 3.5.0, and kotlinx-coroutines to 1.11.0.
* Replace the vendored coffi jar with a git dependency that works with Nix prep.
## `v0.0.1` (2026-03-12)
Initial public release of `trixnity-clj`.
This release establishes the first published Clojure wrapper around Trixnity, with Missionary-based task and flow interop plus the sqlite4clj-backed repository module.
## Upstream API Coverage
# Upstream API Coverage
This page tracks `trixnity-clj` coverage of Trixnity’s API.
`Implemented` means there is a public wrapper under `ol.trixnity.*`.
For upstream `StateFlow` members, the Clojure surface usually appears as a
`current-*` accessor plus a Missionary flow.
`Not yet` means there is no public wrapper today.
## Client
### MatrixClient
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `userId` | `ol.trixnity.client/current-user-id` | Current snapshot only. |
| Not yet | `deviceId` | - | No public wrapper. |
| Not yet | `baseUrl` | - | No public wrapper. |
| Not yet | `identityKey` | - | No public wrapper. |
| Not yet | `signingKey` | - | No public wrapper. |
| Not yet | `di` | - | No public wrapper. |
| Not yet | `api` | - | Used internally by the bridge for some room endpoints, but not exposed publicly. |
| Implemented | `profile` | `ol.trixnity.client/current-profile`, `ol.trixnity.client/profile` | Current snapshot plus relieved flow. |
| Implemented | `serverData` | `ol.trixnity.client/current-server-data`, `ol.trixnity.client/server-data` | Current snapshot plus relieved flow. |
| Implemented | `syncState` | `ol.trixnity.client/current-sync-state`, `ol.trixnity.client/sync-state` | Current snapshot plus relieved flow. |
| Implemented | `initialSyncDone` | `ol.trixnity.client/current-initial-sync-done`, `ol.trixnity.client/initial-sync-done` | Current snapshot plus relieved flow. |
| Implemented | `loginState` | `ol.trixnity.client/current-login-state`, `ol.trixnity.client/login-state` | Current snapshot plus relieved flow. |
| Implemented | `started` | `ol.trixnity.client/current-started`, `ol.trixnity.client/started` | Current snapshot plus relieved flow. |
| Not yet | `logout()` | - | No public wrapper. |
| Not yet | `clearCache()` | - | No public wrapper. |
| Not yet | `clearMediaCache()` | - | No public wrapper. |
| Implemented | `startSync(...)` | `ol.trixnity.client/start-sync` | Uses upstream default presence. There is no public presence parameter. |
| Not yet | `syncOnce(...)` | - | No public wrapper. |
| Not yet | `syncOnce(..., runOnce)` | - | No public wrapper. |
| Implemented | `stopSync()` | `ol.trixnity.client/stop-sync` | - |
| Not yet | `cancelSync()` | - | No public wrapper. |
| Not yet | `setProfileField(...)` | - | No public wrapper. |
| Implemented | `closeSuspending()` | `ol.trixnity.client/close` | Public close path for the client lifecycle. |
### Additional Public APIs
* `ol.trixnity.client/open` wraps client creation or resume around the built-in
sqlite4clj repository configuration.
* `ol.trixnity.client/await-running` waits for `syncState` to reach `RUNNING`.
## Room
### RoomService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `usersTyping` | `ol.trixnity.room/current-users-typing`, `ol.trixnity.room/users-typing` | Current snapshot plus relieved flow. |
| Implemented | `fillTimelineGaps(roomId, eventId, limit)` | `ol.trixnity.room/fill-timeline-gaps` | - |
| Implemented | `getTimelineEvent(roomId, eventId, config)` | `ol.trixnity.room/get-timeline-event` | Flow wrapper over one timeline event lookup. |
| Implemented | `getPreviousTimelineEvent(timelineEvent, config)` | `ol.trixnity.room/get-previous-timeline-event` | - |
| Implemented | `getNextTimelineEvent(timelineEvent, config)` | `ol.trixnity.room/get-next-timeline-event` | - |
| Implemented | `getLastTimelineEvent(roomId, config)` | `ol.trixnity.room/get-last-timeline-event` | Preserves the upstream nested-flow shape. |
| Implemented | `getTimelineEvents(roomId, startFrom, direction, config)` | `ol.trixnity.room/get-timeline-events`, `ol.trixnity.room/get-timeline-events-list` | `get-timeline-events-list` is a list-shaped convenience built on top of the same upstream timeline chain. |
| Implemented | `getLastTimelineEvents(roomId, config)` | `ol.trixnity.room/get-last-timeline-events`, `ol.trixnity.room/get-last-timeline-events-list` | `get-last-timeline-events-list` is a list-shaped convenience over the same upstream room-end traversal. |
| Implemented | `getTimelineEventsFromNowOn(...)` | `ol.trixnity.room/get-timeline-events-from-now-on` | Live timeline stream from the current sync position forward. |
| Implemented | `getTimelineEvents(response, decryptionTimeout)` | `ol.trixnity.room/get-timeline-events` | Covered by the sync-response arity. |
| Not yet | `getTimeline(...)` | - | The advanced mutable `Timeline` abstraction is not exposed yet. |
| Implemented | `getTimelineEventRelations(roomId, eventId, relationType)` | `ol.trixnity.room/get-timeline-event-relations` | - |
| Implemented | `sendMessage(roomId, wait, builder)` | `ol.trixnity.room/send-message`, `ol.trixnity.room/send-reaction` | `send-message` currently covers text messages and reply metadata. Reactions have a dedicated wrapper. |
| Implemented | `cancelSendMessage(roomId, transactionId)` | `ol.trixnity.room/cancel-send-message` | - |
| Implemented | `retrySendMessage(roomId, transactionId)` | `ol.trixnity.room/retry-send-message` | - |
| Implemented | `getAll()` | `ol.trixnity.room/get-all`, `ol.trixnity.room/get-all-flat` | `get-all-flat` flattens the nested upstream flow map. |
| Implemented | `getById(roomId)` | `ol.trixnity.room/get-by-id` | - |
| Implemented | `forgetRoom(roomId, force)` | `ol.trixnity.room/forget-room` | - |
| Implemented | `getAccountData(roomId, contentClass, key)` | `ol.trixnity.room/get-account-data` | - |
| Implemented | `getOutbox()` | `ol.trixnity.room/get-outbox`, `ol.trixnity.room/get-outbox-flat` | `get-outbox-flat` flattens the nested upstream outbox flows. |
| Implemented | `getOutbox(roomId)` | `ol.trixnity.room/get-outbox`, `ol.trixnity.room/get-outbox-flat` | Covered by room-scoped arities. |
| Implemented | `getOutbox(roomId, transactionId)` | `ol.trixnity.room/get-outbox` | Covered by the transaction-id arity. |
| Implemented | `getState(roomId, contentClass, stateKey)` | `ol.trixnity.room/get-state` | - |
| Implemented | `getAllState(roomId, contentClass)` | `ol.trixnity.room/get-all-state` | - |
### Additional Public APIs
| Status | Upstream API | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `MatrixClientServerApiClient.room.createRoom(...)` | `ol.trixnity.room/create-room` | Exposed directly through `client.api.room`, not through `RoomService`. |
| Implemented | `MatrixClientServerApiClient.room.inviteUser(...)` | `ol.trixnity.room/invite-user` | Exposed directly through `client.api.room`, not through `RoomService`. |
| Implemented | `MatrixClientServerApiClient.room.joinRoom(...)` | `ol.trixnity.room/join-room` | Exposed directly through `client.api.room`, not through `RoomService`. |
| Implemented | `de.connect2x.trixnity.client.room.getTimelineEventsAround(...)` | `ol.trixnity.room/get-timeline-events-around` | Centers a list-shaped view around one timeline event. |
## Timeline
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Not yet | `state` | - | No public wrapper for `TimelineState`. |
| Not yet | `init(...)` | - | No public wrapper. |
| Not yet | `loadBefore(...)` | - | No public wrapper. |
| Not yet | `loadAfter(...)` | - | No public wrapper. |
| Not yet | `dropBefore(...)` | - | No public wrapper. |
| Not yet | `dropAfter(...)` | - | No public wrapper. |
## UserService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `loadMembers(roomId, wait)` | `ol.trixnity.user/load-members` | - |
| Implemented | `getAll(roomId)` | `ol.trixnity.user/get-all` | - |
| Implemented | `getById(roomId, userId)` | `ol.trixnity.user/get-by-id` | - |
| Implemented | `getAllReceipts(roomId)` | `ol.trixnity.user/get-all-receipts` | - |
| Implemented | `getReceiptsById(roomId, userId)` | `ol.trixnity.user/get-receipts-by-id` | - |
| Implemented | `getPowerLevel(roomId, userId)` | `ol.trixnity.user/get-power-level` | - |
| Not yet | `getPowerLevel(roomId, createEvent, powerLevels)` | - | Pure helper overload is not wrapped. |
| Implemented | `canKickUser(roomId, userId)` | `ol.trixnity.user/can-kick-user` | - |
| Implemented | `canBanUser(roomId, userId)` | `ol.trixnity.user/can-ban-user` | - |
| Implemented | `canUnbanUser(roomId, userId)` | `ol.trixnity.user/can-unban-user` | - |
| Implemented | `canInviteUser(roomId, userId)` | `ol.trixnity.user/can-invite-user` | - |
| Implemented | `canInvite(roomId)` | `ol.trixnity.user/can-invite` | - |
| Implemented | `canRedactEvent(roomId, eventId)` | `ol.trixnity.user/can-redact-event` | - |
| Implemented | `canSetPowerLevelToMax(roomId, userId)` | `ol.trixnity.user/can-set-power-level-to-max` | - |
| Implemented | `canSendEvent(roomId, KClass)` | `ol.trixnity.user/can-send-event` | Covered by the class-taking arity. |
| Implemented | `canSendEvent(roomId, RoomEventContent)` | `ol.trixnity.user/can-send-event` | Covered by the content-taking arity. |
| Implemented | `getPresence(userId)` | `ol.trixnity.user/get-presence` | - |
| Implemented | `getAccountData(contentClass, key)` | `ol.trixnity.user/get-account-data` | - |
## NotificationService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `getNotifications(decryptionTimeout, syncResponseBufferSize)` | `ol.trixnity.notification/get-notifications` | Deprecated upstream path. Still exposed as a deprecated Clojure wrapper. |
| Implemented | `getNotifications(response, decryptionTimeout)` | `ol.trixnity.notification/get-notifications` | Covered by the response-taking deprecated arity. |
| Implemented | `getAll()` | `ol.trixnity.notification/get-all`, `ol.trixnity.notification/get-all-flat` | `get-all-flat` flattens the nested upstream flows. |
| Implemented | `getById(id)` | `ol.trixnity.notification/get-by-id` | - |
| Implemented | `getCount()` | `ol.trixnity.notification/get-count` | Covered by the one-argument arity. |
| Implemented | `getCount(roomId)` | `ol.trixnity.notification/get-count` | Covered by the room-scoped arity. |
| Implemented | `isUnread(roomId)` | `ol.trixnity.notification/is-unread` | - |
| Implemented | `dismiss(id)` | `ol.trixnity.notification/dismiss` | - |
| Implemented | `dismissAll()` | `ol.trixnity.notification/dismiss-all` | - |
| Implemented | `getAllUpdates()` | `ol.trixnity.notification/get-all-updates` | - |
| Not yet | `onPush(roomId, eventId)` | - | No public wrapper. |
| Not yet | `processPending()` | - | No public wrapper. |
## VerificationService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `activeDeviceVerification` | `ol.trixnity.verification/current-active-device-verification`, `ol.trixnity.verification/active-device-verification` | Current snapshot plus relieved flow. |
| Implemented | `activeUserVerifications` | `ol.trixnity.verification/current-active-user-verifications`, `ol.trixnity.verification/active-user-verifications` | Current snapshot plus relieved flow. |
| Not yet | `createDeviceVerificationRequest(...)` | - | No public wrapper. |
| Not yet | `createUserVerificationRequest(...)` | - | No public wrapper. |
| Implemented | `getSelfVerificationMethods()` | `ol.trixnity.verification/get-self-verification-methods` | - |
| Not yet | `getActiveUserVerification(timelineEvent)` | - | No public wrapper. |
| Not yet | `getActiveUserVerification(roomId, eventId)` | - | No public wrapper. |
## KeyService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `bootstrapRunning` | `ol.trixnity.key/current-bootstrap-running`, `ol.trixnity.key/bootstrap-running` | Current snapshot plus relieved flow. |
| Not yet | `bootstrapCrossSigning(secretKey, secretKeyEvent)` | - | No public wrapper. |
| Not yet | `bootstrapCrossSigning()` | - | No public wrapper. |
| Not yet | `bootstrapCrossSigningFromPassphrase(passphrase)` | - | No public wrapper. |
| Implemented | `getTrustLevel(userId, deviceId)` | `ol.trixnity.key/get-trust-level` | Covered by the three-argument arity when the first id is a user id. |
| Implemented | `getTrustLevel(roomId, eventId)` | `ol.trixnity.key/get-trust-level` | Covered by the three-argument arity when the first id is a room id. |
| Implemented | `getTrustLevel(userId)` | `ol.trixnity.key/get-trust-level` | Covered by the two-argument arity. |
| Implemented | `getDeviceKeys(userId)` | `ol.trixnity.key/get-device-keys` | - |
| Implemented | `getCrossSigningKeys(userId)` | `ol.trixnity.key/get-cross-signing-keys` | - |
## KeyBackupService
| Status | Upstream member | `trixnity-clj` surface | Notes |
| --- | --- | --- | --- |
| Implemented | `version` | `ol.trixnity.key/current-backup-version`, `ol.trixnity.key/backup-version` | Current snapshot plus relieved flow. |
| Not yet | `loadMegolmSession(roomId, sessionId)` | - | No public wrapper. |
| Not yet | `keyBackupCanBeTrusted(version, recoveryKey)` | - | No public wrapper. |
| Not yet | `bootstrapRoomKeyBackup(...)` | - | No public wrapper. |
## Examples
# Examples
This page collects focused examples for APIs that need more room than the README.
## Matrix Spaces
```clojure
(ns my.spaces
(:require
[missionary.core :as m]
[ol.trixnity.room :as room]
[ol.trixnity.schemas :as mx]
[ol.trixnity.space :as space])
(:import
[java.time Duration]))
(defn create-project-space [client]
(m/? (space/create-space
client
{::mx/room-name "Project"})))
(defn create-project-docs-subspace [client project-space-id]
(m/? (space/create-subspace
client
project-space-id
{::mx/room-name "Project Docs"
::mx/via #{"matrix.example.org"}})))
(defn add-existing-room [client project-space-id room-id]
(m/? (space/set-child
client
project-space-id
room-id
{::mx/via #{"matrix.example.org"}
::mx/order "a"
::mx/suggested true}
{::mx/timeout (Duration/ofSeconds 5)})))
(defn set-canonical-parent [client room-id project-space-id]
(m/? (space/set-parent
client
room-id
project-space-id
{::mx/via #{"matrix.example.org"}
::mx/canonical true})))
(defn remove-room-from-space [client project-space-id room-id]
(m/? (space/remove-child client project-space-id room-id)))
(defn remove-parent-link [client room-id project-space-id]
(m/? (space/remove-parent client room-id project-space-id)))
(defn read-first-hierarchy-page [client project-space-id]
(m/? (space/hierarchy
client
project-space-id
{::mx/limit 20
::mx/max-depth 2
::mx/suggested-only false})))
(defn invite-space-member [client project-space-id]
;; Spaces are rooms, so member actions use ol.trixnity.room directly.
(m/? (room/invite-user
client
project-space-id
"@alice:example.org")))
```
## ol.trixnity
# ol.trixnity
> A Clojure adapter for Trixnity, a Matrix SDK. Write Matrix apps and bots in Clojure.



[Trixnity](https://trixnity.connect2x.de/) is a Kotlin Multiplatform library for building Matrix applications. It covers the full Matrix client-server API surface, including room management, sync, and end-to-end encryption. Despite flying under the radar, Trixnity already powers a large chunk of Germany’s TI-Messenger healthcare infrastructure, reaching tens of millions of potential users.
Trixnity targets the JVM, which means Clojure can call into it directly, but the interop is still rough. Nearly every interesting API is either a Kotlin suspend function or returns a `Flow` or `StateFlow`, so even basic client work quickly turns into coroutine plumbing: scopes, dispatchers, cancellation, and stream observation.
The flow-heavy parts are especially awkward because they are long-lived Kotlin stream types, not something Clojure can consume idiomatically. So things like sync state, timeline updates, and other live observations need extra translation. Kotlin’s compiler also mangles names in the compiled bytecode, which makes direct interop harder when reading the upstream docs.
`trixnity-clj` wraps Trixnity with a small Kotlin bridge and exposes the concurrency model using [Missionary](https://github.com/leonoel/missionary/). Kotlin suspend functions become Missionary tasks. Kotlin `Flow` and `StateFlow` surfaces become Missionary flows. `StateFlow` properties are exposed as a synchronous `current-*` getter plus a relieved Missionary flow.
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
## Installation
`trixnity-clj` currently prepares its Kotlin bridge via `:deps/prep-lib`, so users must:
* have `mvn` available on `PATH`
* run `clojure -X:deps prep` before first use
```clojure
;; deps.edn
{:deps {com.outskirtslabs/trixnity-clj
{:git/url "https://github.com/outskirtslabs/trixnity-clj.git"
:git/sha "f279fce88481396c60511cb57af71dd5de8e2222"}}}
```
## Repository Backend
In Trixnity, a repository module is the persistence backend for all the client state it needs to keep around: sync tokens, rooms, timelines, outbox entries, encryption keys, notifications, and the rest of the Matrix baggage.
Upstream ships several of these across platforms. If you want durable storage on the JVM, the stock option is the [Exposed repository module](https://gitlab.com/trixnity/trixnity/-/tree/main/trixnity-client/trixnity-client-repository-exposed), which means JetBrains’s ORM Exposed layered on top of JDBC.
Maybe that sparks joy for somebody. It does not spark joy for me.
For the kind of bots and small clients I am building, running SQLite through JDBC makes write serialization harder than it should be, which means more `busy_timeout` pain.
And god forbid anyone suggest dragging Hikari into that mess.
So this library includes its own repository implementation backed by [sqlite4clj](https://github.com/andersmurphy/sqlite4clj), and that is the default setup.
The storage layer is just plain SQLite, explicit SQL, and a lot less nonsense.
Shout out to Anders Murphy for building [sqlite4clj](https://github.com/andersmurphy/sqlite4clj), and making the SQLite story in Clojure 100% less deranged.
## Example Usage
Use [`ol.trixnity.repo`](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/repo.clj) to configure the built-in sqlite4clj repository, then open a client with [`ol.trixnity.client/open`](https://github.com/outskirtslabs/trixnity-clj/blob/main/src/clj/ol/trixnity/client.clj):
```clojure
(ns my.bot
(:require
[missionary.core :as m]
[ol.trixnity.client :as client]
[ol.trixnity.event :as event]
[ol.trixnity.repo :as repo]
[ol.trixnity.room :as room]
[ol.trixnity.room.message :as msg]
[ol.trixnity.schemas :as mx])
(:import
[java.time Duration]))
(defn run-bot []
(let [client (m/? (client/open
(merge
{::mx/homeserver-url "https://matrix.example.org"
::mx/user-id "@bot:example.org"
::mx/password "secret"}
(repo/sqlite4clj-config
{:database-path "./var/trixnity.sqlite"
:media-path "./var/media"}))))]
(m/? (client/start-sync client))
(m/? (client/await-running client
{::mx/timeout (Duration/ofSeconds 30)}))
(future
(m/? (m/reduce
(fn [_ ev]
(when (event/text? ev)
(let [{::mx/keys [transaction-id status]}
(m/? (room/send-message
client
(event/room-id ev)
(-> (msg/text "pong")
(msg/reply-to ev))
{::mx/timeout (Duration/ofSeconds 5)}))]
(println "Queued reply with transaction id:" transaction-id)
(future
(m/? (m/reduce
(fn [_ outbox-snapshot]
(when-let [progress (::mx/media-upload-progress outbox-snapshot)]
(println "Attachment upload progress:" progress))
nil)
nil
status)))))
nil)
nil
(room/get-timeline-events-from-now-on
client
{::mx/decryption-timeout (Duration/ofSeconds 8)}))))
{:client client}))
```
If you want a different repository implementation, construct a `MatrixClient` yourself and pass it to `client/open` as `::mx/client`.
For more focused examples, including Matrix space administration, see [Examples](https://docs.outskirtslabs.com/ol.trixnity/next/examples).
## Documentation
* [Docs](https://docs.outskirtslabs.com/ol.trixnity/next/)
* [API Reference](https://docs.outskirtslabs.com/ol.trixnity/next/api)
* [Support via GitHub Issues](https://github.com/outskirtslabs/trixnity-clj/issues)
## Examples
Several examples are available in [examples](examples/):
* [basic-bot](examples/basic-bot/) - A minimal end-to-end bot that opens a client, starts sync, joins invited rooms, and replies to live timeline events.
## License
Copyright (C) 2026 Casey Link
Distributed under the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) just like Trixnity itself.
## Security policy
# Security policy
## Advisories
All security advisories for `trixnity-clj` will be posted [on GitHub](https://github.com/outskirtslabs/trixnity-clj/security/advisories).
## Reporting a vulnerability
Please report possible security vulnerabilities [via GitHub](https://github.com/outskirtslabs/trixnity-clj/security/advisories), or by emailing me at `casey@outskirtslabs.com`. You may encrypt email with [my public PGP key](https://casey.link/pgp.asc).
For the organization-wide security policy, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
Thank you!
[Casey Link](https://casey.link)
## ol.vips.codegen
# ol.vips.codegen
Generate the introspected wrapper namespaces for `ol.vips`.
This namespace queries the live libvips runtime through [`ol.vips`](api/ol-vips.adoc) and
`ol.vips.impl.introspect`, then writes the generated public wrapper files:
* `src/ol/vips/enums.clj`
* `src/ol/vips/operations.clj`
The generated files are derived from the currently loaded libvips build, so
run this from a development environment where `ol.vips` can initialize its
native libraries.
Usage:
```clojure
(require '[ol.vips.codegen :as codegen])
(codegen/generate!)
;;=> {:enum-count 42
;; :operation-count 233
;; :skipped-count 57
;; :paths ["src/ol/vips/enums.clj"
;; "src/ol/vips/operations.clj"]}
```
In the ol.vips code repo the babashka task `bb codegen` is the normal project entry point for this
namespace.
## generate!
```clojure
(generate!)
```
Generates `ol.vips.enums` and `ol.vips.operations` from live libvips introspection.
Initializes the runtime if needed, discovers the supported operations and
enums from the current libvips build, writes the generated source files, and
returns a summary map describing what was emitted.
Example:
```clojure
(generate!)
;;=> {:enum-count ...
;; :operation-count ...
;; :skipped-count ...
;; :paths ["src/ol/vips/enums.clj" "src/ol/vips/operations.clj"]}
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/codegen.clj#L441-L481)
---
## -main
```clojure
(-main & _args)
```
Runs [`generate!`](#generate!) and prints a short CLI summary.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/codegen.clj#L483-L490)
## ol.vips.enums
# ol.vips.enums
Generated enum descriptors for normalized libvips enum ids.
Use [`registry`](#registry) or the generated enum vars to inspect enum metadata. Use [`ol.vips/encode-enum`](api/ol-vips.adoc#encode-enum) and [`ol.vips/decode-enum`](api/ol-vips.adoc#decode-enum) to convert between keywords and the underlying libvips integer values.
## registry
Generated registry of libvips enum descriptors keyed by normalized enum id.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L11-L54)
---
## access
Generated enum descriptor for `VipsAccess`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:random`
- `:sequential`
- `:sequential-unbuffered`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L56-L65)
---
## align
Generated enum descriptor for `VipsAlign`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:centre`
- `:high`
- `:low`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L67-L76)
---
## angle
Generated enum descriptor for `VipsAngle`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:d0`
- `:d180`
- `:d270`
- `:d90`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L78-L88)
---
## angle45
Generated enum descriptor for `VipsAngle45`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:d0`
- `:d135`
- `:d180`
- `:d225`
- `:d270`
- `:d315`
- `:d45`
- `:d90`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L90-L104)
---
## band-format
Generated enum descriptor for `VipsBandFormat`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:char`
- `:complex`
- `:double`
- `:dpcomplex`
- `:float`
- `:int`
- `:notset`
- `:short`
- `:uchar`
- `:uint`
- `:ushort`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L106-L123)
---
## blend-mode
Generated enum descriptor for `VipsBlendMode`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:add`
- `:atop`
- `:clear`
- `:colour-burn`
- `:colour-dodge`
- `:darken`
- `:dest`
- `:dest-atop`
- `:dest-in`
- `:dest-out`
- `:dest-over`
- `:difference`
- `:exclusion`
- `:hard-light`
- `:in`
- `:lighten`
- `:multiply`
- `:out`
- `:over`
- `:overlay`
- `:saturate`
- `:screen`
- `:soft-light`
- `:source`
- `:xor`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L125-L156)
---
## coding
Generated enum descriptor for `VipsCoding`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:error`
- `:labq`
- `:none`
- `:rad`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L158-L168)
---
## combine
Generated enum descriptor for `VipsCombine`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:max`
- `:min`
- `:sum`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L170-L179)
---
## combine-mode
Generated enum descriptor for `VipsCombineMode`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:add`
- `:set`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L181-L189)
---
## compass-direction
Generated enum descriptor for `VipsCompassDirection`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:centre`
- `:east`
- `:north`
- `:north-east`
- `:north-west`
- `:south`
- `:south-east`
- `:south-west`
- `:west`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L191-L206)
---
## direction
Generated enum descriptor for `VipsDirection`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:horizontal`
- `:vertical`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L208-L216)
---
## extend
Generated enum descriptor for `VipsExtend`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:background`
- `:black`
- `:copy`
- `:mirror`
- `:repeat`
- `:white`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L218-L230)
---
## fail-on
Generated enum descriptor for `VipsFailOn`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:error`
- `:none`
- `:truncated`
- `:warning`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L232-L242)
---
## foreign-dz-container
Generated enum descriptor for `VipsForeignDzContainer`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:fs`
- `:szi`
- `:zip`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L244-L253)
---
## foreign-dz-depth
Generated enum descriptor for `VipsForeignDzDepth`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:one`
- `:onepixel`
- `:onetile`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L255-L264)
---
## foreign-dz-layout
Generated enum descriptor for `VipsForeignDzLayout`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:dz`
- `:google`
- `:iiif`
- `:iiif3`
- `:zoomify`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L266-L277)
---
## foreign-heif-compression
Generated enum descriptor for `VipsForeignHeifCompression`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:av1`
- `:avc`
- `:hevc`
- `:jpeg`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L279-L289)
---
## foreign-heif-encoder
Generated enum descriptor for `VipsForeignHeifEncoder`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:aom`
- `:auto`
- `:rav1e`
- `:svt`
- `:x265`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L291-L302)
---
## foreign-subsample
Generated enum descriptor for `VipsForeignSubsample`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:auto`
- `:off`
- `:on`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L304-L313)
---
## foreign-tiff-compression
Generated enum descriptor for `VipsForeignTiffCompression`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:ccittfax4`
- `:deflate`
- `:jp2k`
- `:jpeg`
- `:lzw`
- `:none`
- `:packbits`
- `:webp`
- `:zstd`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L315-L330)
---
## foreign-tiff-predictor
Generated enum descriptor for `VipsForeignTiffPredictor`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:float`
- `:horizontal`
- `:none`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L332-L341)
---
## foreign-tiff-resunit
Generated enum descriptor for `VipsForeignTiffResunit`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cm`
- `:inch`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L343-L351)
---
## foreign-webp-preset
Generated enum descriptor for `VipsForeignWebpPreset`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:default`
- `:drawing`
- `:icon`
- `:photo`
- `:picture`
- `:text`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L353-L365)
---
## intent
Generated enum descriptor for `VipsIntent`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:absolute`
- `:auto`
- `:perceptual`
- `:relative`
- `:saturation`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L367-L378)
---
## interesting
Generated enum descriptor for `VipsInteresting`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:all`
- `:attention`
- `:centre`
- `:entropy`
- `:high`
- `:low`
- `:none`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L380-L393)
---
## interpretation
Generated enum descriptor for `VipsInterpretation`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:b-w`
- `:cmc`
- `:cmyk`
- `:error`
- `:fourier`
- `:grey16`
- `:histogram`
- `:hsv`
- `:lab`
- `:labq`
- `:labs`
- `:lch`
- `:matrix`
- `:multiband`
- `:oklab`
- `:oklch`
- `:rgb`
- `:rgb16`
- `:scrgb`
- `:srgb`
- `:xyz`
- `:yxy`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L395-L423)
---
## kernel
Generated enum descriptor for `VipsKernel`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cubic`
- `:lanczos2`
- `:lanczos3`
- `:linear`
- `:mitchell`
- `:mks2013`
- `:mks2021`
- `:nearest`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L425-L439)
---
## operation-boolean
Generated enum descriptor for `VipsOperationBoolean`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:and`
- `:eor`
- `:lshift`
- `:or`
- `:rshift`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L441-L452)
---
## operation-complex
Generated enum descriptor for `VipsOperationComplex`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:conj`
- `:polar`
- `:rect`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L454-L463)
---
## operation-complex2
Generated enum descriptor for `VipsOperationComplex2`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cross-phase`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L465-L472)
---
## operation-complexget
Generated enum descriptor for `VipsOperationComplexget`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:imag`
- `:real`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L474-L482)
---
## operation-math
Generated enum descriptor for `VipsOperationMath`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:acos`
- `:acosh`
- `:asin`
- `:asinh`
- `:atan`
- `:atanh`
- `:cos`
- `:cosh`
- `:exp`
- `:exp10`
- `:log`
- `:log10`
- `:sin`
- `:sinh`
- `:tan`
- `:tanh`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L484-L506)
---
## operation-math2
Generated enum descriptor for `VipsOperationMath2`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:atan2`
- `:pow`
- `:wop`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L508-L517)
---
## operation-morphology
Generated enum descriptor for `VipsOperationMorphology`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:dilate`
- `:erode`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L519-L527)
---
## operation-relational
Generated enum descriptor for `VipsOperationRelational`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:equal`
- `:less`
- `:lesseq`
- `:more`
- `:moreeq`
- `:noteq`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L529-L541)
---
## operation-round
Generated enum descriptor for `VipsOperationRound`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:ceil`
- `:floor`
- `:rint`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L543-L552)
---
## pcs
Generated enum descriptor for `VipsPCS`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:lab`
- `:xyz`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L554-L562)
---
## precision
Generated enum descriptor for `VipsPrecision`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:approximate`
- `:float`
- `:integer`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L564-L573)
---
## region-shrink
Generated enum descriptor for `VipsRegionShrink`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:max`
- `:mean`
- `:median`
- `:min`
- `:mode`
- `:nearest`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L575-L587)
---
## sdf-shape
Generated enum descriptor for `VipsSdfShape`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:box`
- `:circle`
- `:line`
- `:rounded-box`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L589-L599)
---
## size
Generated enum descriptor for `VipsSize`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:both`
- `:down`
- `:force`
- `:up`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L601-L611)
---
## text-wrap
Generated enum descriptor for `VipsTextWrap`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:char`
- `:none`
- `:word`
- `:word-char`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L613-L623)
---
## enums
```clojure
(enums)
```
Lists the generated libvips enum ids.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/enums.clj#L625-L625)
## ol.vips.operations
# ol.vips.operations
Generated libvips operation wrappers keyed by normalized operation id.
Inspect [`registry`](#registry) directly for generated operation metadata, then call the generated wrapper vars directly for typed access to supported libvips operations.
## registry
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L12-L252)
---
## abs
```clojure
(abs input)
```
absolute value of an image
Operation name: `abs`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L254-L270)
---
## add
```clojure
(add left right)
```
add two images
Operation name: `add`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L272-L289)
---
## addalpha
```clojure
(addalpha input)
```
append an alpha channel
Operation name: `addalpha`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L291-L307)
---
## affine
```clojure
(affine input matrix)
(affine input matrix opts)
```
affine transform of an image
Operation name: `affine`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `matrix` -> `:matrix` (seqable of number): Transformation matrix
Optional inputs:
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
- `background` -> `:background` (seqable of number): Background value
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L309-L333)
---
## arrayjoin
```clojure
(arrayjoin input)
(arrayjoin input opts)
```
join an array of images
Operation name: `arrayjoin`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- `across` -> `:across` (integer): Number of images across grid
- `shim` -> `:shim` (integer): Pixels between images
- `background` -> `:background` (seqable of number): Colour for new pixels
- `halign` -> `:halign` (keyword, see `ol.vips.enums/align`): Align on the left, centre or right
- `valign` -> `:valign` (keyword, see `ol.vips.enums/align`): Align on the top, centre or bottom
- `hspacing` -> `:hspacing` (integer): Horizontal spacing between images
- `vspacing` -> `:vspacing` (integer): Vertical spacing between images
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L335-L358)
---
## autorot
```clojure
(autorot input)
```
autorotate image by exif tag
Operation name: `autorot`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Angle image was rotated by
- `flip` -> `:flip` (boolean): Whether the image was flipped or not
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L360-L378)
---
## avg
```clojure
(avg input)
```
find image average
Operation name: `avg`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L380-L396)
---
## bandbool
```clojure
(bandbool input boolean)
```
boolean operation across image bands
Operation name: `bandbool`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L398-L415)
---
## bandfold
```clojure
(bandfold input)
(bandfold input opts)
```
fold up x axis into bands
Operation name: `bandfold`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `factor` -> `:factor` (integer): Fold by this factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L417-L434)
---
## bandjoin
```clojure
(bandjoin input)
```
bandwise join a set of images
Operation name: `bandjoin`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L436-L452)
---
## bandjoin-const
```clojure
(bandjoin-const input c)
```
append a constant band to an image
Operation name: `bandjoin_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `c` -> `:c` (seqable of number): Array of constants to add
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L454-L471)
---
## bandmean
```clojure
(bandmean input)
```
band-wise average
Operation name: `bandmean`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L473-L489)
---
## bandrank
```clojure
(bandrank input)
(bandrank input opts)
```
band-wise rank of a set of images
Operation name: `bandrank`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- `index` -> `:index` (integer): Select this band element from sorted list
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L491-L508)
---
## bandunfold
```clojure
(bandunfold input)
(bandunfold input opts)
```
unfold image bands into x axis
Operation name: `bandunfold`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `factor` -> `:factor` (integer): Unfold by this factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L510-L527)
---
## black
```clojure
(black width height)
(black width height opts)
```
make a black image
Operation name: `black`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `bands` -> `:bands` (integer): Number of bands in image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L529-L547)
---
## boolean
```clojure
(boolean left right boolean)
```
boolean operation on two images
Operation name: `boolean`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L549-L567)
---
## boolean-const
```clojure
(boolean-const input boolean c)
```
boolean operations against a constant
Operation name: `boolean_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L569-L587)
---
## buildlut
```clojure
(buildlut input)
```
build a look-up table
Operation name: `buildlut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Matrix of XY coordinates
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L589-L605)
---
## byteswap
```clojure
(byteswap input)
```
byteswap an image
Operation name: `byteswap`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L607-L623)
---
## cache
```clojure
(cache input)
(cache input opts)
```
cache an image
Operation name: `cache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-tiles` -> `:max-tiles` (integer): Maximum number of tiles to cache
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L625-L644)
---
## canny
```clojure
(canny input)
(canny input opts)
```
Canny edge detector
Operation name: `canny`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L646-L664)
---
## case
```clojure
(case index cases)
```
use pixel values to pick cases from an array of images
Operation name: `case`.
Returns an image handle.
Required inputs:
- `index` -> `:index` (image): Index image
- `cases` -> `:cases` (seqable of image): Array of case images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L666-L683)
---
## cast
```clojure
(cast input format)
(cast input format opts)
```
cast an image
Operation name: `cast`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Format to cast to
Optional inputs:
- `shift` -> `:shift` (boolean): Shift integer values up and down
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L685-L703)
---
## clamp
```clojure
(clamp input)
(clamp input opts)
```
clamp values of an image
Operation name: `clamp`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `min` -> `:min` (float): Minimum value
- `max` -> `:max` (float): Maximum value
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L705-L723)
---
## cmc2-lch
```clojure
(cmc2-lch input)
```
transform LCh to CMC
Operation name: `CMC2LCh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L725-L741)
---
## cmyk2-xyz
```clojure
(cmyk2-xyz input)
```
transform CMYK to XYZ
Operation name: `CMYK2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L743-L759)
---
## colourspace
```clojure
(colourspace input space)
(colourspace input space opts)
```
convert to a new colorspace
Operation name: `colourspace`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `space` -> `:space` (keyword, see `ol.vips.enums/interpretation`): Destination color space
Optional inputs:
- `source-space` -> `:source-space` (keyword, see `ol.vips.enums/interpretation`): Source color space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L761-L779)
---
## compass
```clojure
(compass input mask)
(compass input mask opts)
```
convolve with rotating mask
Operation name: `compass`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `times` -> `:times` (integer): Rotate and convolve this many times
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle45`): Rotate mask by this much between convolutions
- `combine` -> `:combine` (keyword, see `ol.vips.enums/combine`): Combine convolution results like this
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L781-L804)
---
## complex
```clojure
(complex input cmplx)
```
perform a complex operation on an image
Operation name: `complex`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `cmplx` -> `:cmplx` (keyword, see `ol.vips.enums/operation-complex`): Complex to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L806-L823)
---
## complex2
```clojure
(complex2 left right cmplx)
```
complex binary operations on two images
Operation name: `complex2`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `cmplx` -> `:cmplx` (keyword, see `ol.vips.enums/operation-complex2`): Binary complex operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L825-L843)
---
## complexform
```clojure
(complexform left right)
```
form a complex image from two real images
Operation name: `complexform`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L845-L862)
---
## complexget
```clojure
(complexget input get)
```
get a component from a complex image
Operation name: `complexget`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `get` -> `:get` (keyword, see `ol.vips.enums/operation-complexget`): Complex to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L864-L881)
---
## composite2
```clojure
(composite2 base overlay mode)
(composite2 base overlay mode opts)
```
blend a pair of images with a blend mode
Operation name: `composite2`.
Returns an image handle.
Required inputs:
- `base` -> `:base` (image): Base image
- `overlay` -> `:overlay` (image): Overlay image
- `mode` -> `:mode` (keyword, see `ol.vips.enums/blend-mode`): VipsBlendMode to join with
Optional inputs:
- `x` -> `:x` (integer): x position of overlay
- `y` -> `:y` (integer): y position of overlay
- `compositing-space` -> `:compositing-space` (keyword, see `ol.vips.enums/interpretation`): Composite images in this colour space
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L883-L905)
---
## conv
```clojure
(conv input mask)
(conv input mask opts)
```
convolution operation
Operation name: `conv`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L907-L927)
---
## conva
```clojure
(conva input mask)
(conva input mask opts)
```
approximate integer convolution
Operation name: `conva`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L929-L948)
---
## convasep
```clojure
(convasep input mask)
(convasep input mask opts)
```
approximate separable integer convolution
Operation name: `convasep`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `layers` -> `:layers` (integer): Use this many layers in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L950-L968)
---
## convf
```clojure
(convf input mask)
```
float convolution operation
Operation name: `convf`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L970-L987)
---
## convi
```clojure
(convi input mask)
```
int convolution operation
Operation name: `convi`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L989-L1006)
---
## convsep
```clojure
(convsep input mask)
(convsep input mask opts)
```
separable convolution operation
Operation name: `convsep`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1008-L1028)
---
## copy
```clojure
(copy input)
(copy input opts)
```
copy an image
Operation name: `copy`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `swap` -> `:swap` (boolean): Swap bytes in image between little and big-endian
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `bands` -> `:bands` (integer): Number of bands in image
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Pixel format in image
- `coding` -> `:coding` (keyword, see `ol.vips.enums/coding`): Pixel coding
- `interpretation` -> `:interpretation` (keyword, see `ol.vips.enums/interpretation`): Pixel interpretation
- `xres` -> `:xres` (float): Horizontal resolution in pixels/mm
- `yres` -> `:yres` (float): Vertical resolution in pixels/mm
- `xoffset` -> `:xoffset` (integer): Horizontal offset of origin
- `yoffset` -> `:yoffset` (integer): Vertical offset of origin
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1030-L1057)
---
## countlines
```clojure
(countlines input direction)
```
count lines in an image
Operation name: `countlines`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Countlines left-right or up-down
Optional inputs:
- none
Outputs:
- `nolines` -> `:nolines` (float): Number of lines
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1059-L1076)
---
## csvload
```clojure
(csvload filename)
(csvload filename opts)
```
load csv
Operation name: `csvload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `skip` -> `:skip` (integer): Skip this many lines at the start of the file
- `lines` -> `:lines` (integer): Read this many lines from the file
- `whitespace` -> `:whitespace` (string): Set of whitespace characters
- `separator` -> `:separator` (string): Set of separator characters
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1078-L1106)
---
## csvsave
```clojure
(csvsave input filename)
(csvsave input filename opts)
```
save image to csv
Operation name: `csvsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `separator` -> `:separator` (string): Separator characters
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1108-L1131)
---
## d-e00
```clojure
(d-e00 left right)
```
calculate dE00
Operation name: `dE00`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1133-L1150)
---
## d-e76
```clojure
(d-e76 left right)
```
calculate dE76
Operation name: `dE76`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1152-L1169)
---
## d-ecmc
```clojure
(d-ecmc left right)
```
calculate dECMC
Operation name: `dECMC`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1171-L1188)
---
## deviate
```clojure
(deviate input)
```
find image standard deviation
Operation name: `deviate`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1190-L1206)
---
## divide
```clojure
(divide left right)
```
divide two images
Operation name: `divide`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1208-L1225)
---
## draw-circle
```clojure
(draw-circle image ink cx cy radius)
(draw-circle image ink cx cy radius opts)
```
draw a circle on an image
Operation name: `draw_circle`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `cx` -> `:cx` (integer): Centre of draw_circle
- `cy` -> `:cy` (integer): Centre of draw_circle
- `radius` -> `:radius` (integer): Radius in pixels
Optional inputs:
- `fill` -> `:fill` (boolean): Draw a solid object
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1227-L1248)
---
## draw-flood
```clojure
(draw-flood image ink x y)
(draw-flood image ink x y opts)
```
flood-fill an area
Operation name: `draw_flood`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `x` -> `:x` (integer): DrawFlood start point
- `y` -> `:y` (integer): DrawFlood start point
Optional inputs:
- `test` -> `:test` (image): Test pixels in this image
- `equal` -> `:equal` (boolean): DrawFlood while equal to edge
Outputs:
- `left` -> `:left` (integer): Left edge of modified area
- `top` -> `:top` (integer): Top edge of modified area
- `width` -> `:width` (integer): Width of modified area
- `height` -> `:height` (integer): Height of modified area
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1250-L1274)
---
## draw-image
```clojure
(draw-image image sub x y)
(draw-image image sub x y opts)
```
paint an image into another image
Operation name: `draw_image`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `sub` -> `:sub` (image): Sub-image to insert into main image
- `x` -> `:x` (integer): Draw image here
- `y` -> `:y` (integer): Draw image here
Optional inputs:
- `mode` -> `:mode` (keyword, see `ol.vips.enums/combine-mode`): Combining mode
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1276-L1296)
---
## draw-line
```clojure
(draw-line image ink x1 y1 x2 y2)
```
draw a line on an image
Operation name: `draw_line`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `x1` -> `:x1` (integer): Start of draw_line
- `y1` -> `:y1` (integer): Start of draw_line
- `x2` -> `:x2` (integer): End of draw_line
- `y2` -> `:y2` (integer): End of draw_line
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1298-L1319)
---
## draw-mask
```clojure
(draw-mask image ink mask x y)
```
draw a mask on an image
Operation name: `draw_mask`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `mask` -> `:mask` (image): Mask of pixels to draw
- `x` -> `:x` (integer): Draw mask here
- `y` -> `:y` (integer): Draw mask here
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1321-L1341)
---
## draw-rect
```clojure
(draw-rect image ink left top width height)
(draw-rect image ink left top width height opts)
```
paint a rectangle on an image
Operation name: `draw_rect`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `left` -> `:left` (integer): Rect to fill
- `top` -> `:top` (integer): Rect to fill
- `width` -> `:width` (integer): Rect to fill
- `height` -> `:height` (integer): Rect to fill
Optional inputs:
- `fill` -> `:fill` (boolean): Draw a solid object
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1343-L1365)
---
## draw-smudge
```clojure
(draw-smudge image left top width height)
```
blur a rectangle on an image
Operation name: `draw_smudge`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `left` -> `:left` (integer): Rect to fill
- `top` -> `:top` (integer): Rect to fill
- `width` -> `:width` (integer): Rect to fill
- `height` -> `:height` (integer): Rect to fill
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1367-L1387)
---
## dzsave
```clojure
(dzsave input filename)
(dzsave input filename opts)
```
save image to deepzoom file
Operation name: `dzsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `dirname` -> `:dirname` (string): Directory name to save to
- `imagename` -> `:imagename` (string): Image name
- `layout` -> `:layout` (keyword, see `ol.vips.enums/foreign-dz-layout`): Directory layout
- `suffix` -> `:suffix` (string): Filename suffix for tiles
- `overlap` -> `:overlap` (integer): Tile overlap in pixels
- `tile-size` -> `:tile-size` (integer): Tile size in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `centre` -> `:centre` (boolean): Center image in tile
- `depth` -> `:depth` (keyword, see `ol.vips.enums/foreign-dz-depth`): Pyramid depth
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Rotate image during save
- `container` -> `:container` (keyword, see `ol.vips.enums/foreign-dz-container`): Pyramid container type
- `properties` -> `:properties` (boolean): Write a properties file to the output directory
- `compression` -> `:compression` (integer): ZIP deflate compression level
- `region-shrink` -> `:region-shrink` (keyword, see `ol.vips.enums/region-shrink`): Method to shrink regions
- `skip-blanks` -> `:skip-blanks` (integer): Skip tiles which are nearly equal to the background
- `id` -> `:id` (string): Resource ID
- `q` -> `:Q` (integer): Q factor
- `no-strip` -> `:no-strip` (boolean): Don’t strip tile metadata
- `basename` -> `:basename` (string): Base name to save to
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1389-L1431)
---
## embed
```clojure
(embed input x y width height)
(embed input x y width height opts)
```
embed an image in a larger image
Operation name: `embed`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `x` -> `:x` (integer): Left edge of input in output
- `y` -> `:y` (integer): Top edge of input in output
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
- `background` -> `:background` (seqable of number): Color for background pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1433-L1455)
---
## extract-area
```clojure
(extract-area input left top width height)
```
extract an area from an image
Operation name: `extract_area`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `left` -> `:left` (integer): Left edge of extract area
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1457-L1477)
---
## extract-band
```clojure
(extract-band input band)
(extract-band input band opts)
```
extract band from an image
Operation name: `extract_band`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `band` -> `:band` (integer): Band to extract
Optional inputs:
- `n` -> `:n` (integer): Number of bands to extract
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1479-L1497)
---
## eye
```clojure
(eye width height)
(eye width height opts)
```
make an image showing the eye’s spatial response
Operation name: `eye`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `factor` -> `:factor` (float): Maximum spatial frequency
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1499-L1518)
---
## falsecolour
```clojure
(falsecolour input)
```
false-color an image
Operation name: `falsecolour`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1520-L1536)
---
## fastcor
```clojure
(fastcor input ref)
```
fast correlation
Operation name: `fastcor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `ref` -> `:ref` (image): Input reference image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1538-L1555)
---
## fill-nearest
```clojure
(fill-nearest input)
```
fill image zeros with nearest non-zero pixel
Operation name: `fill_nearest`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Value of nearest non-zero pixel
- `distance` -> `:distance` (image): Distance to nearest non-zero pixel
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1557-L1574)
---
## find-trim
```clojure
(find-trim input)
(find-trim input opts)
```
search an image for non-edge areas
Operation name: `find_trim`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to find_trim
Optional inputs:
- `threshold` -> `:threshold` (float): Object threshold
- `background` -> `:background` (seqable of number): Color for background pixels
- `line-art` -> `:line-art` (boolean): Enable line art mode
Outputs:
- `left` -> `:left` (integer): Left edge of image
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1576-L1598)
---
## flatten
```clojure
(flatten input)
(flatten input opts)
```
flatten alpha out of an image
Operation name: `flatten`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1600-L1618)
---
## flip
```clojure
(flip input direction)
```
flip an image
Operation name: `flip`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Direction to flip image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1620-L1637)
---
## float2rad
```clojure
(float2rad input)
```
transform float RGB to Radiance coding
Operation name: `float2rad`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1639-L1655)
---
## fractsurf
```clojure
(fractsurf width height fractal-dimension)
```
make a fractal surface
Operation name: `fractsurf`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `fractal-dimension` -> `:fractal-dimension` (float): Fractal dimension
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1657-L1675)
---
## freqmult
```clojure
(freqmult input mask)
```
frequency-domain filtering
Operation name: `freqmult`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `mask` -> `:mask` (image): Input mask image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1677-L1694)
---
## gamma
```clojure
(gamma input)
(gamma input opts)
```
gamma an image
Operation name: `gamma`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `exponent` -> `:exponent` (float): Gamma factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1696-L1713)
---
## gaussblur
```clojure
(gaussblur input sigma)
(gaussblur input sigma opts)
```
gaussian blur
Operation name: `gaussblur`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `sigma` -> `:sigma` (float): Sigma of Gaussian
Optional inputs:
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1715-L1734)
---
## gaussmat
```clojure
(gaussmat sigma min-ampl)
(gaussmat sigma min-ampl opts)
```
make a gaussian image
Operation name: `gaussmat`.
Returns an image handle.
Required inputs:
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
Optional inputs:
- `separable` -> `:separable` (boolean): Generate separable Gaussian
- `integer` -> `:integer` (boolean): Generate integer Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Generate with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1736-L1756)
---
## gaussnoise
```clojure
(gaussnoise width height)
(gaussnoise width height opts)
```
make a gaussnoise image
Operation name: `gaussnoise`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `sigma` -> `:sigma` (float): Standard deviation of pixels in generated image
- `mean` -> `:mean` (float): Mean of pixels in generated image
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1758-L1778)
---
## gifload
```clojure
(gifload filename)
(gifload filename opts)
```
load GIF with libnsgif
Operation name: `gifload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `page` -> `:page` (integer): First page to load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1780-L1806)
---
## gifsave
```clojure
(gifsave input filename)
(gifsave input filename opts)
```
save as gif
Operation name: `gifsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `dither` -> `:dither` (float): Amount of dithering
- `effort` -> `:effort` (integer): Quantisation effort
- `bitdepth` -> `:bitdepth` (integer): Number of bits per pixel
- `interframe-maxerror` -> `:interframe-maxerror` (float): Maximum inter-frame error for transparency
- `reuse` -> `:reuse` (boolean): Reuse palette from input
- `interpalette-maxerror` -> `:interpalette-maxerror` (float): Maximum inter-palette error for palette reusage
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) GIF
- `reoptimise` -> `:reoptimise` (boolean): Reoptimise colour palettes
- `keep-duplicate-frames` -> `:keep-duplicate-frames` (boolean): Keep duplicate frames in the output instead of combining them
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1808-L1839)
---
## globalbalance
```clojure
(globalbalance input)
(globalbalance input opts)
```
global balance an image mosaic
Operation name: `globalbalance`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `gamma` -> `:gamma` (float): Image gamma
- `int-output` -> `:int-output` (boolean): Integer output
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1841-L1859)
---
## gravity
```clojure
(gravity input direction width height)
(gravity input direction width height opts)
```
place an image within a larger image with a certain gravity
Operation name: `gravity`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/compass-direction`): Direction to place image within width/height
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
- `background` -> `:background` (seqable of number): Color for background pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1861-L1882)
---
## grey
```clojure
(grey width height)
(grey width height opts)
```
make a grey ramp image
Operation name: `grey`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1884-L1902)
---
## grid
```clojure
(grid input tile-height across down)
```
grid an image
Operation name: `grid`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `tile-height` -> `:tile-height` (integer): Chop into tiles this high
- `across` -> `:across` (integer): Number of tiles across
- `down` -> `:down` (integer): Number of tiles down
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1904-L1923)
---
## heifload
```clojure
(heifload filename)
(heifload filename opts)
```
load a HEIF image
Operation name: `heifload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `thumbnail` -> `:thumbnail` (boolean): Fetch thumbnail image
- `autorotate` -> `:autorotate` (boolean): Rotate image using exif orientation
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1925-L1954)
---
## heifsave
```clojure
(heifsave input filename)
(heifsave input filename opts)
```
save image in HEIF format
Operation name: `heifsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `bitdepth` -> `:bitdepth` (integer): Number of bits per pixel
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `compression` -> `:compression` (keyword, see `ol.vips.enums/foreign-heif-compression`): Compression format
- `effort` -> `:effort` (integer): CPU effort
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `speed` -> `:speed` (integer): CPU effort
- `encoder` -> `:encoder` (keyword, see `ol.vips.enums/foreign-heif-encoder`): Select encoder to use
- `tune` -> `:tune` (string): Tuning parameters
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1956-L1987)
---
## hist-cum
```clojure
(hist-cum input)
```
form cumulative histogram
Operation name: `hist_cum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L1989-L2005)
---
## hist-entropy
```clojure
(hist-entropy input)
```
estimate image entropy
Operation name: `hist_entropy`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input histogram image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2007-L2023)
---
## hist-equal
```clojure
(hist-equal input)
(hist-equal input opts)
```
histogram equalisation
Operation name: `hist_equal`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Equalise with this band
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2025-L2042)
---
## hist-find
```clojure
(hist-find input)
(hist-find input opts)
```
find image histogram
Operation name: `hist_find`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Find histogram of band
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2044-L2061)
---
## hist-find-indexed
```clojure
(hist-find-indexed input index)
(hist-find-indexed input index opts)
```
find indexed image histogram
Operation name: `hist_find_indexed`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `index` -> `:index` (image): Index image
Optional inputs:
- `combine` -> `:combine` (keyword, see `ol.vips.enums/combine`): Combine bins like this
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2063-L2081)
---
## hist-find-ndim
```clojure
(hist-find-ndim input)
(hist-find-ndim input opts)
```
find n-dimensional image histogram
Operation name: `hist_find_ndim`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `bins` -> `:bins` (integer): Number of bins in each dimension
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2083-L2100)
---
## hist-ismonotonic
```clojure
(hist-ismonotonic input)
```
test for monotonicity
Operation name: `hist_ismonotonic`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input histogram image
Optional inputs:
- none
Outputs:
- `monotonic` -> `:monotonic` (boolean): true if in is monotonic
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2102-L2118)
---
## hist-local
```clojure
(hist-local input width height)
(hist-local input width height opts)
```
local histogram equalisation
Operation name: `hist_local`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
Optional inputs:
- `max-slope` -> `:max-slope` (integer): Maximum slope (CLAHE)
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2120-L2139)
---
## hist-match
```clojure
(hist-match input ref)
```
match two histograms
Operation name: `hist_match`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input histogram
- `ref` -> `:ref` (image): Reference histogram
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2141-L2158)
---
## hist-norm
```clojure
(hist-norm input)
```
normalise histogram
Operation name: `hist_norm`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2160-L2176)
---
## hist-plot
```clojure
(hist-plot input)
```
plot histogram
Operation name: `hist_plot`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2178-L2194)
---
## hough-circle
```clojure
(hough-circle input)
(hough-circle input opts)
```
find hough circle transform
Operation name: `hough_circle`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `scale` -> `:scale` (integer): Scale down dimensions by this factor
- `min-radius` -> `:min-radius` (integer): Smallest radius to search for
- `max-radius` -> `:max-radius` (integer): Largest radius to search for
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2196-L2215)
---
## hough-line
```clojure
(hough-line input)
(hough-line input opts)
```
find hough line transform
Operation name: `hough_line`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `width` -> `:width` (integer): Horizontal size of parameter space
- `height` -> `:height` (integer): Vertical size of parameter space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2217-L2235)
---
## hsv2s-rgb
```clojure
(hsv2s-rgb input)
```
transform HSV to sRGB
Operation name: `HSV2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2237-L2253)
---
## icc-export
```clojure
(icc-export input)
(icc-export input opts)
```
output to device with ICC profile
Operation name: `icc_export`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `output-profile` -> `:output-profile` (string): Filename to load output profile from
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2255-L2276)
---
## icc-import
```clojure
(icc-import input)
(icc-import input opts)
```
import from device with ICC profile
Operation name: `icc_import`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `embedded` -> `:embedded` (boolean): Use embedded input profile, if available
- `input-profile` -> `:input-profile` (string): Filename to load input profile from
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2278-L2299)
---
## icc-transform
```clojure
(icc-transform input output-profile)
(icc-transform input output-profile opts)
```
transform between devices with ICC profiles
Operation name: `icc_transform`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `output-profile` -> `:output-profile` (string): Filename to load output profile from
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `embedded` -> `:embedded` (boolean): Use embedded input profile, if available
- `input-profile` -> `:input-profile` (string): Filename to load input profile from
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2301-L2324)
---
## identity
```clojure
(identity)
(identity opts)
```
make a 1D image where pixel values are indexes
Operation name: `identity`.
Returns an image handle.
Required inputs:
- none
Optional inputs:
- `bands` -> `:bands` (integer): Number of bands in LUT
- `ushort` -> `:ushort` (boolean): Create a 16-bit LUT
- `size` -> `:size` (integer): Size of 16-bit LUT
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2326-L2345)
---
## ifthenelse
```clojure
(ifthenelse cond in1 in2)
(ifthenelse cond in1 in2 opts)
```
ifthenelse an image
Operation name: `ifthenelse`.
Returns an image handle.
Required inputs:
- `cond` -> `:cond` (image): Condition input image
- `in1` -> `:in1` (image): Source for TRUE pixels
- `in2` -> `:in2` (image): Source for FALSE pixels
Optional inputs:
- `blend` -> `:blend` (boolean): Blend smoothly between then and else parts
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2347-L2366)
---
## insert
```clojure
(insert main sub x y)
(insert main sub x y opts)
```
insert image @sub into @main at @x, @y
Operation name: `insert`.
Returns an image handle.
Required inputs:
- `main` -> `:main` (image): Main input image
- `sub` -> `:sub` (image): Sub-image to insert into main image
- `x` -> `:x` (integer): Left edge of sub in main
- `y` -> `:y` (integer): Top edge of sub in main
Optional inputs:
- `expand` -> `:expand` (boolean): Expand output to hold all of both inputs
- `background` -> `:background` (seqable of number): Color for new pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2368-L2389)
---
## invert
```clojure
(invert input)
```
invert an image
Operation name: `invert`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2391-L2407)
---
## invertlut
```clojure
(invertlut input)
(invertlut input opts)
```
build an inverted look-up table
Operation name: `invertlut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Matrix of XY coordinates
Optional inputs:
- `size` -> `:size` (integer): LUT size to generate
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2409-L2426)
---
## join
```clojure
(join in1 in2 direction)
(join in1 in2 direction opts)
```
join a pair of images
Operation name: `join`.
Returns an image handle.
Required inputs:
- `in1` -> `:in1` (image): First input image
- `in2` -> `:in2` (image): Second input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Join left-right or up-down
Optional inputs:
- `expand` -> `:expand` (boolean): Expand output to hold all of both inputs
- `shim` -> `:shim` (integer): Pixels between images
- `background` -> `:background` (seqable of number): Colour for new pixels
- `align` -> `:align` (keyword, see `ol.vips.enums/align`): Align on the low, centre or high coordinate edge
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2428-L2450)
---
## jpegload
```clojure
(jpegload filename)
(jpegload filename opts)
```
load jpeg from file
Operation name: `jpegload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `autorotate` -> `:autorotate` (boolean): Rotate image using exif orientation
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2452-L2479)
---
## jpegsave
```clojure
(jpegsave input filename)
(jpegsave input filename opts)
```
save as jpeg
Operation name: `jpegsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `optimize-coding` -> `:optimize-coding` (boolean): Compute optimal Huffman coding tables
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) jpeg
- `no-subsample` -> `:no-subsample` (boolean): Disable chroma subsample
- `trellis-quant` -> `:trellis-quant` (boolean): Apply trellis quantisation to each 8x8 block
- `overshoot-deringing` -> `:overshoot-deringing` (boolean): Apply overshooting to samples with extreme values
- `optimize-scans` -> `:optimize-scans` (boolean): Split spectrum of DCT coefficients into separate scans
- `quant-table` -> `:quant-table` (integer): Use predefined quantization table with given index
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `restart-interval` -> `:restart-interval` (integer): Add restart markers every specified number of mcu
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2481-L2513)
---
## jpegsave-mime
```clojure
(jpegsave-mime input)
(jpegsave-mime input opts)
```
save image to jpeg mime
Operation name: `jpegsave_mime`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `optimize-coding` -> `:optimize-coding` (boolean): Compute optimal Huffman coding tables
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) jpeg
- `no-subsample` -> `:no-subsample` (boolean): Disable chroma subsample
- `trellis-quant` -> `:trellis-quant` (boolean): Apply trellis quantisation to each 8x8 block
- `overshoot-deringing` -> `:overshoot-deringing` (boolean): Apply overshooting to samples with extreme values
- `optimize-scans` -> `:optimize-scans` (boolean): Split spectrum of DCT coefficients into separate scans
- `quant-table` -> `:quant-table` (integer): Use predefined quantization table with given index
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `restart-interval` -> `:restart-interval` (integer): Add restart markers every specified number of mcu
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2515-L2546)
---
## lab-q2-lab
```clojure
(lab-q2-lab input)
```
unpack a LabQ image to float Lab
Operation name: `LabQ2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2548-L2564)
---
## lab-q2-lab-s
```clojure
(lab-q2-lab-s input)
```
unpack a LabQ image to short Lab
Operation name: `LabQ2LabS`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2566-L2582)
---
## lab-q2s-rgb
```clojure
(lab-q2s-rgb input)
```
convert a LabQ image to sRGB
Operation name: `LabQ2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2584-L2600)
---
## lab-s2-lab
```clojure
(lab-s2-lab input)
```
transform signed short Lab to float
Operation name: `LabS2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2602-L2618)
---
## lab-s2-lab-q
```clojure
(lab-s2-lab-q input)
```
transform short Lab to LabQ coding
Operation name: `LabS2LabQ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2620-L2636)
---
## lab2-lab-q
```clojure
(lab2-lab-q input)
```
transform float Lab to LabQ coding
Operation name: `Lab2LabQ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2638-L2654)
---
## lab2-lab-s
```clojure
(lab2-lab-s input)
```
transform float Lab to signed short
Operation name: `Lab2LabS`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2656-L2672)
---
## lab2-lch
```clojure
(lab2-lch input)
```
transform Lab to LCh
Operation name: `Lab2LCh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2674-L2690)
---
## lab2-xyz
```clojure
(lab2-xyz input)
(lab2-xyz input opts)
```
transform CIELAB to XYZ
Operation name: `Lab2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `temp` -> `:temp` (seqable of number): Color temperature
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2692-L2709)
---
## labelregions
```clojure
(labelregions input)
```
label regions in an image
Operation name: `labelregions`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `mask` -> `:mask` (image): Mask of region labels
- `segments` -> `:segments` (integer): Number of discrete contiguous regions
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2711-L2728)
---
## lch2-cmc
```clojure
(lch2-cmc input)
```
transform LCh to CMC
Operation name: `LCh2CMC`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2730-L2746)
---
## lch2-lab
```clojure
(lch2-lab input)
```
transform LCh to Lab
Operation name: `LCh2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2748-L2764)
---
## linear
```clojure
(linear input a b)
(linear input a b opts)
```
calculate (a * in + b)
Operation name: `linear`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `a` -> `:a` (seqable of number): Multiply by this
- `b` -> `:b` (seqable of number): Add this
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output should be uchar
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2766-L2785)
---
## linecache
```clojure
(linecache input)
(linecache input opts)
```
cache an image as a set of lines
Operation name: `linecache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
- `threaded` -> `:threaded` (boolean): Allow threaded access
- `persistent` -> `:persistent` (boolean): Keep cache between evaluations
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2787-L2807)
---
## logmat
```clojure
(logmat sigma min-ampl)
(logmat sigma min-ampl opts)
```
make a Laplacian of Gaussian image
Operation name: `logmat`.
Returns an image handle.
Required inputs:
- `sigma` -> `:sigma` (float): Radius of Gaussian
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
Optional inputs:
- `separable` -> `:separable` (boolean): Generate separable Gaussian
- `integer` -> `:integer` (boolean): Generate integer Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Generate with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2809-L2829)
---
## mapim
```clojure
(mapim input index)
(mapim input index opts)
```
resample with a map image
Operation name: `mapim`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `index` -> `:index` (image): Index pixels with this
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2831-L2851)
---
## maplut
```clojure
(maplut input lut)
(maplut input lut opts)
```
map an image though a lut
Operation name: `maplut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `lut` -> `:lut` (image): Look-up table image
Optional inputs:
- `band` -> `:band` (integer): Apply one-band lut to this band of in
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2853-L2871)
---
## mask-butterworth
```clojure
(mask-butterworth width height order frequency-cutoff amplitude-cutoff)
(mask-butterworth width height order frequency-cutoff amplitude-cutoff opts)
```
make a butterworth filter
Operation name: `mask_butterworth`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2873-L2897)
---
## mask-butterworth-band
```clojure
(mask-butterworth-band width height order frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff)
(mask-butterworth-band width height order frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff opts)
```
make a butterworth_band filter
Operation name: `mask_butterworth_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2899-L2925)
---
## mask-butterworth-ring
```clojure
(mask-butterworth-ring width height order frequency-cutoff amplitude-cutoff ringwidth)
(mask-butterworth-ring width height order frequency-cutoff amplitude-cutoff ringwidth opts)
```
make a butterworth ring filter
Operation name: `mask_butterworth_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2927-L2952)
---
## mask-fractal
```clojure
(mask-fractal width height fractal-dimension)
(mask-fractal width height fractal-dimension opts)
```
make fractal filter
Operation name: `mask_fractal`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `fractal-dimension` -> `:fractal-dimension` (float): Fractal dimension
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2954-L2976)
---
## mask-gaussian
```clojure
(mask-gaussian width height frequency-cutoff amplitude-cutoff)
(mask-gaussian width height frequency-cutoff amplitude-cutoff opts)
```
make a gaussian filter
Operation name: `mask_gaussian`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L2978-L3001)
---
## mask-gaussian-band
```clojure
(mask-gaussian-band width height frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff)
(mask-gaussian-band width height frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff opts)
```
make a gaussian filter
Operation name: `mask_gaussian_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3003-L3028)
---
## mask-gaussian-ring
```clojure
(mask-gaussian-ring width height frequency-cutoff amplitude-cutoff ringwidth)
(mask-gaussian-ring width height frequency-cutoff amplitude-cutoff ringwidth opts)
```
make a gaussian ring filter
Operation name: `mask_gaussian_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3030-L3054)
---
## mask-ideal
```clojure
(mask-ideal width height frequency-cutoff)
(mask-ideal width height frequency-cutoff opts)
```
make an ideal filter
Operation name: `mask_ideal`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3056-L3078)
---
## mask-ideal-band
```clojure
(mask-ideal-band width height frequency-cutoff-x frequency-cutoff-y radius)
(mask-ideal-band width height frequency-cutoff-x frequency-cutoff-y radius opts)
```
make an ideal band filter
Operation name: `mask_ideal_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3080-L3104)
---
## mask-ideal-ring
```clojure
(mask-ideal-ring width height frequency-cutoff ringwidth)
(mask-ideal-ring width height frequency-cutoff ringwidth opts)
```
make an ideal ring filter
Operation name: `mask_ideal_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3106-L3129)
---
## match
```clojure
(match ref sec xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2)
(match ref sec xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2 opts)
```
first-order match of two images
Operation name: `match`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `xr1` -> `:xr1` (integer): Position of first reference tie-point
- `yr1` -> `:yr1` (integer): Position of first reference tie-point
- `xs1` -> `:xs1` (integer): Position of first secondary tie-point
- `ys1` -> `:ys1` (integer): Position of first secondary tie-point
- `xr2` -> `:xr2` (integer): Position of second reference tie-point
- `yr2` -> `:yr2` (integer): Position of second reference tie-point
- `xs2` -> `:xs2` (integer): Position of second secondary tie-point
- `ys2` -> `:ys2` (integer): Position of second secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `search` -> `:search` (boolean): Search to improve tie-points
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3131-L3159)
---
## math
```clojure
(math input math)
```
apply a math operation to an image
Operation name: `math`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `math` -> `:math` (keyword, see `ol.vips.enums/operation-math`): Math to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3161-L3178)
---
## math2
```clojure
(math2 left right math2)
```
binary math operations
Operation name: `math2`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `math2` -> `:math2` (keyword, see `ol.vips.enums/operation-math2`): Math to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3180-L3198)
---
## math2-const
```clojure
(math2-const input math2 c)
```
binary math operations with a constant
Operation name: `math2_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `math2` -> `:math2` (keyword, see `ol.vips.enums/operation-math2`): Math to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3200-L3218)
---
## matrixinvert
```clojure
(matrixinvert input)
```
invert a matrix
Operation name: `matrixinvert`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): An square matrix
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output matrix
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3220-L3236)
---
## matrixload
```clojure
(matrixload filename)
(matrixload filename opts)
```
load matrix
Operation name: `matrixload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3238-L3262)
---
## matrixmultiply
```clojure
(matrixmultiply left right)
```
multiply two matrices
Operation name: `matrixmultiply`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): First matrix to multiply
- `right` -> `:right` (image): Second matrix to multiply
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output matrix
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3264-L3281)
---
## matrixprint
```clojure
(matrixprint input)
(matrixprint input opts)
```
print matrix
Operation name: `matrixprint`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3283-L3304)
---
## matrixsave
```clojure
(matrixsave input filename)
(matrixsave input filename opts)
```
save image to matrix
Operation name: `matrixsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3306-L3328)
---
## maxpair
```clojure
(maxpair left right)
```
maximum of a pair of images
Operation name: `maxpair`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3330-L3347)
---
## measure
```clojure
(measure input h v)
(measure input h v opts)
```
measure a set of patches on a color chart
Operation name: `measure`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Image to measure
- `h` -> `:h` (integer): Number of patches across chart
- `v` -> `:v` (integer): Number of patches down chart
Optional inputs:
- `left` -> `:left` (integer): Left edge of extract area
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Outputs:
- `out` -> `:out` (image): Output array of statistics
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3349-L3371)
---
## merge
```clojure
(merge ref sec direction dx dy)
(merge ref sec direction dx dy opts)
```
merge two images
Operation name: `merge`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical merge
- `dx` -> `:dx` (integer): Horizontal displacement from sec to ref
- `dy` -> `:dy` (integer): Vertical displacement from sec to ref
Optional inputs:
- `mblend` -> `:mblend` (integer): Maximum blend size
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3373-L3394)
---
## minpair
```clojure
(minpair left right)
```
minimum of a pair of images
Operation name: `minpair`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3396-L3413)
---
## morph
```clojure
(morph input mask morph)
```
morphology operation
Operation name: `morph`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
- `morph` -> `:morph` (keyword, see `ol.vips.enums/operation-morphology`): Morphological operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3415-L3433)
---
## mosaic
```clojure
(mosaic ref sec direction xref yref xsec ysec)
(mosaic ref sec direction xref yref xsec ysec opts)
```
mosaic two images
Operation name: `mosaic`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical mosaic
- `xref` -> `:xref` (integer): Position of reference tie-point
- `yref` -> `:yref` (integer): Position of reference tie-point
- `xsec` -> `:xsec` (integer): Position of secondary tie-point
- `ysec` -> `:ysec` (integer): Position of secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `mblend` -> `:mblend` (integer): Maximum blend size
- `bandno` -> `:bandno` (integer): Band to search for features on
Outputs:
- `out` -> `:out` (image): Output image
- `dx0` -> `:dx0` (integer): Detected integer offset
- `dy0` -> `:dy0` (integer): Detected integer offset
- `scale1` -> `:scale1` (float): Detected scale
- `angle1` -> `:angle1` (float): Detected rotation
- `dy1` -> `:dy1` (float): Detected first-order displacement
- `dx1` -> `:dx1` (float): Detected first-order displacement
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3435-L3467)
---
## mosaic1
```clojure
(mosaic1 ref sec direction xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2)
(mosaic1 ref sec direction xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2 opts)
```
first-order mosaic of two images
Operation name: `mosaic1`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical mosaic
- `xr1` -> `:xr1` (integer): Position of first reference tie-point
- `yr1` -> `:yr1` (integer): Position of first reference tie-point
- `xs1` -> `:xs1` (integer): Position of first secondary tie-point
- `ys1` -> `:ys1` (integer): Position of first secondary tie-point
- `xr2` -> `:xr2` (integer): Position of second reference tie-point
- `yr2` -> `:yr2` (integer): Position of second reference tie-point
- `xs2` -> `:xs2` (integer): Position of second secondary tie-point
- `ys2` -> `:ys2` (integer): Position of second secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `search` -> `:search` (boolean): Search to improve tie-points
- `mblend` -> `:mblend` (integer): Maximum blend size
- `bandno` -> `:bandno` (integer): Band to search for features on
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3469-L3500)
---
## msb
```clojure
(msb input)
(msb input opts)
```
pick most-significant byte from an image
Operation name: `msb`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Band to msb
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3502-L3519)
---
## multiply
```clojure
(multiply left right)
```
multiply two images
Operation name: `multiply`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3521-L3538)
---
## oklab2-oklch
```clojure
(oklab2-oklch input)
```
transform Oklab to Oklch
Operation name: `Oklab2Oklch`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3540-L3556)
---
## oklab2-xyz
```clojure
(oklab2-xyz input)
```
transform Oklab to XYZ
Operation name: `Oklab2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3558-L3574)
---
## oklch2-oklab
```clojure
(oklch2-oklab input)
```
transform Oklch to Oklab
Operation name: `Oklch2Oklab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3576-L3592)
---
## percent
```clojure
(percent input percent)
```
find threshold for percent of pixels
Operation name: `percent`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
- `percent` -> `:percent` (float): Percent of pixels
Optional inputs:
- none
Outputs:
- `threshold` -> `:threshold` (integer): Threshold above which lie percent of pixels
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3594-L3611)
---
## perlin
```clojure
(perlin width height)
(perlin width height opts)
```
make a perlin noise image
Operation name: `perlin`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `cell-size` -> `:cell-size` (integer): Size of Perlin cells
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3613-L3633)
---
## phasecor
```clojure
(phasecor input in2)
```
calculate phase correlation
Operation name: `phasecor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `in2` -> `:in2` (image): Second input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3635-L3652)
---
## pngload
```clojure
(pngload filename)
(pngload filename opts)
```
load png from file
Operation name: `pngload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3654-L3679)
---
## pngsave
```clojure
(pngsave input filename)
(pngsave input filename opts)
```
save image to file as png
Operation name: `pngsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `compression` -> `:compression` (integer): Compression factor
- `interlace` -> `:interlace` (boolean): Interlace image
- `filter` -> `:filter` (integer flags): libpng row filter flag(s)
- `palette` -> `:palette` (boolean): Quantise to 8bpp palette
- `colours` -> `:colours` (integer): Max number of palette colours
- `q` -> `:Q` (integer): Quantisation quality
- `dither` -> `:dither` (float): Amount of dithering
- `bitdepth` -> `:bitdepth` (integer): Write as a 1, 2, 4, 8 or 16 bit image
- `effort` -> `:effort` (integer): Quantisation CPU effort
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3681-L3712)
---
## premultiply
```clojure
(premultiply input)
(premultiply input opts)
```
premultiply image alpha
Operation name: `premultiply`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3714-L3731)
---
## prewitt
```clojure
(prewitt input)
```
Prewitt edge detector
Operation name: `prewitt`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3733-L3749)
---
## profile
```clojure
(profile input)
```
find image profiles
Operation name: `profile`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `columns` -> `:columns` (image): First non-zero pixel in column
- `rows` -> `:rows` (image): First non-zero pixel in row
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3751-L3768)
---
## project
```clojure
(project input)
```
find image projections
Operation name: `project`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `columns` -> `:columns` (image): Sums of columns
- `rows` -> `:rows` (image): Sums of rows
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3770-L3787)
---
## quadratic
```clojure
(quadratic input coeff)
```
resample an image with a quadratic transform
Operation name: `quadratic`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `coeff` -> `:coeff` (image): Coefficient matrix
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3789-L3806)
---
## rad2float
```clojure
(rad2float input)
```
unpack Radiance coding to float RGB
Operation name: `rad2float`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3808-L3824)
---
## rank
```clojure
(rank input width height index)
```
rank filter
Operation name: `rank`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
- `index` -> `:index` (integer): Select pixel at index
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3826-L3845)
---
## rawload
```clojure
(rawload filename width height bands)
(rawload filename width height bands opts)
```
load raw data from a file
Operation name: `rawload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `bands` -> `:bands` (integer): Number of bands in image
Optional inputs:
- `offset` -> `:offset` (integer): Offset in bytes from start of file
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Pixel format in image
- `interpretation` -> `:interpretation` (keyword, see `ol.vips.enums/interpretation`): Pixel interpretation
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3847-L3877)
---
## rawsave
```clojure
(rawsave input filename)
(rawsave input filename opts)
```
save image to raw file
Operation name: `rawsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3879-L3901)
---
## recomb
```clojure
(recomb input m)
```
linear recombination with matrix
Operation name: `recomb`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `m` -> `:m` (image): Matrix of coefficients
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3903-L3920)
---
## reduce
```clojure
(reduce input xshrink hshrink yshrink vshrink)
(reduce input xshrink hshrink yshrink vshrink opts)
```
reduce an image
Operation name: `reduce`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3922-L3945)
---
## reduceh
```clojure
(reduceh input xshrink hshrink)
(reduceh input xshrink hshrink opts)
```
shrink an image horizontally
Operation name: `reduceh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3947-L3968)
---
## reducev
```clojure
(reducev input yshrink vshrink)
(reducev input yshrink vshrink opts)
```
shrink an image vertically
Operation name: `reducev`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3970-L3991)
---
## relational
```clojure
(relational left right relational)
```
relational operation on two images
Operation name: `relational`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `relational` -> `:relational` (keyword, see `ol.vips.enums/operation-relational`): Relational to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L3993-L4011)
---
## relational-const
```clojure
(relational-const input relational c)
```
relational operations against a constant
Operation name: `relational_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `relational` -> `:relational` (keyword, see `ol.vips.enums/operation-relational`): Relational to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4013-L4031)
---
## remainder
```clojure
(remainder left right)
```
remainder after integer division of two images
Operation name: `remainder`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4033-L4050)
---
## remainder-const
```clojure
(remainder-const input c)
```
remainder after integer division of an image and a constant
Operation name: `remainder_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4052-L4069)
---
## remosaic
```clojure
(remosaic input old-str new-str)
```
rebuild an mosaiced image
Operation name: `remosaic`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `old-str` -> `:old-str` (string): Search for this string
- `new-str` -> `:new-str` (string): And swap for this string
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4071-L4089)
---
## replicate
```clojure
(replicate input across down)
```
replicate an image
Operation name: `replicate`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `across` -> `:across` (integer): Repeat this many times horizontally
- `down` -> `:down` (integer): Repeat this many times vertically
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4091-L4109)
---
## resize
```clojure
(resize input scale)
(resize input scale opts)
```
resize an image
Operation name: `resize`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `scale` -> `:scale` (float): Scale image by this factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
- `vscale` -> `:vscale` (float): Vertical scale image by this factor
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4111-L4134)
---
## rot
```clojure
(rot input angle)
```
rotate an image
Operation name: `rot`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Angle to rotate image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4136-L4153)
---
## rot45
```clojure
(rot45 input)
(rot45 input opts)
```
rotate an image
Operation name: `rot45`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle45`): Angle to rotate image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4155-L4172)
---
## rotate
```clojure
(rotate input angle)
(rotate input angle opts)
```
rotate an image by a number of degrees
Operation name: `rotate`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `angle` -> `:angle` (float): Rotate clockwise by this many degrees
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4174-L4196)
---
## round
```clojure
(round input round)
```
perform a round function on an image
Operation name: `round`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `round` -> `:round` (keyword, see `ol.vips.enums/operation-round`): Rounding operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4198-L4215)
---
## s-rgb2-hsv
```clojure
(s-rgb2-hsv input)
```
transform sRGB to HSV
Operation name: `sRGB2HSV`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4217-L4233)
---
## s-rgb2sc-rgb
```clojure
(s-rgb2sc-rgb input)
```
convert an sRGB image to scRGB
Operation name: `sRGB2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4235-L4251)
---
## sc-rgb2-bw
```clojure
(sc-rgb2-bw input)
(sc-rgb2-bw input opts)
```
convert scRGB to BW
Operation name: `scRGB2BW`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4253-L4270)
---
## sc-rgb2-xyz
```clojure
(sc-rgb2-xyz input)
```
transform scRGB to XYZ
Operation name: `scRGB2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4272-L4288)
---
## sc-rgb2s-rgb
```clojure
(sc-rgb2s-rgb input)
(sc-rgb2s-rgb input opts)
```
convert scRGB to sRGB
Operation name: `scRGB2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4290-L4307)
---
## scale
```clojure
(scale input)
(scale input opts)
```
scale an image to uchar
Operation name: `scale`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `exp` -> `:exp` (float): Exponent for log scale
- `log` -> `:log` (boolean): Log scale
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4309-L4327)
---
## scharr
```clojure
(scharr input)
```
Scharr edge detector
Operation name: `scharr`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4329-L4345)
---
## sdf
```clojure
(sdf width height shape)
(sdf width height shape opts)
```
create an SDF image
Operation name: `sdf`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `shape` -> `:shape` (keyword, see `ol.vips.enums/sdf-shape`): SDF shape to create
Optional inputs:
- `r` -> `:r` (float): Radius
- `a` -> `:a` (seqable of number): Point a
- `b` -> `:b` (seqable of number): Point b
- `corners` -> `:corners` (seqable of number): Corner radii
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4347-L4369)
---
## sequential
```clojure
(sequential input)
(sequential input opts)
```
check sequential access
Operation name: `sequential`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `trace` -> `:trace` (boolean): Trace pixel requests
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4371-L4390)
---
## sharpen
```clojure
(sharpen input)
(sharpen input opts)
```
unsharp masking for print
Operation name: `sharpen`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `radius` -> `:radius` (integer): Radius of Gaussian
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `x1` -> `:x1` (float): Flat/jaggy threshold
- `y2` -> `:y2` (float): Maximum brightening
- `y3` -> `:y3` (float): Maximum darkening
- `m1` -> `:m1` (float): Slope for flat areas
- `m2` -> `:m2` (float): Slope for jaggy areas
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4392-L4415)
---
## shrink
```clojure
(shrink input xshrink hshrink yshrink vshrink)
(shrink input xshrink hshrink yshrink vshrink opts)
```
shrink an image
Operation name: `shrink`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4417-L4438)
---
## shrinkh
```clojure
(shrinkh input xshrink hshrink)
(shrinkh input xshrink hshrink opts)
```
shrink an image horizontally
Operation name: `shrinkh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (integer): Horizontal shrink factor
- `hshrink` -> `:hshrink` (integer): Horizontal shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4440-L4459)
---
## shrinkv
```clojure
(shrinkv input yshrink vshrink)
(shrinkv input yshrink vshrink opts)
```
shrink an image vertically
Operation name: `shrinkv`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `yshrink` -> `:yshrink` (integer): Vertical shrink factor
- `vshrink` -> `:vshrink` (integer): Vertical shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4461-L4480)
---
## sign
```clojure
(sign input)
```
unit vector of pixel
Operation name: `sign`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4482-L4498)
---
## similarity
```clojure
(similarity input)
(similarity input opts)
```
similarity transform of an image
Operation name: `similarity`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- `scale` -> `:scale` (float): Scale by this factor
- `angle` -> `:angle` (float): Rotate clockwise by this many degrees
- `background` -> `:background` (seqable of number): Background value
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4500-L4523)
---
## sines
```clojure
(sines width height)
(sines width height opts)
```
make a 2D sine wave
Operation name: `sines`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `hfreq` -> `:hfreq` (float): Horizontal spatial frequency
- `vfreq` -> `:vfreq` (float): Vertical spatial frequency
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4525-L4545)
---
## smartcrop
```clojure
(smartcrop input width height)
(smartcrop input width height opts)
```
extract an area from an image
Operation name: `smartcrop`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:input` (image): Input image
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Optional inputs:
- `interesting` -> `:interesting` (keyword, see `ol.vips.enums/interesting`): How to measure interestingness
- `premultiplied` -> `:premultiplied` (boolean): Input image already has premultiplied alpha
Outputs:
- `out` -> `:out` (image): Output image
- `attention-x` -> `:attention-x` (integer): Horizontal position of attention centre
- `attention-y` -> `:attention-y` (integer): Vertical position of attention centre
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4547-L4569)
---
## sobel
```clojure
(sobel input)
```
Sobel edge detector
Operation name: `sobel`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4571-L4587)
---
## spcor
```clojure
(spcor input ref)
```
spatial correlation
Operation name: `spcor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `ref` -> `:ref` (image): Input reference image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4589-L4606)
---
## spectrum
```clojure
(spectrum input)
```
make displayable power spectrum
Operation name: `spectrum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4608-L4624)
---
## stats
```clojure
(stats input)
```
find many image stats
Operation name: `stats`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output array of statistics
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4626-L4642)
---
## stdif
```clojure
(stdif input width height)
(stdif input width height opts)
```
statistical difference
Operation name: `stdif`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
Optional inputs:
- `s0` -> `:s0` (float): New deviation
- `b` -> `:b` (float): Weight of new deviation
- `m0` -> `:m0` (float): New mean
- `a` -> `:a` (float): Weight of new mean
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4644-L4666)
---
## subsample
```clojure
(subsample input xfac yfac)
(subsample input xfac yfac opts)
```
subsample an image
Operation name: `subsample`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `xfac` -> `:xfac` (integer): Horizontal subsample factor
- `yfac` -> `:yfac` (integer): Vertical subsample factor
Optional inputs:
- `point` -> `:point` (boolean): Point sample
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4668-L4687)
---
## subtract
```clojure
(subtract left right)
```
subtract two images
Operation name: `subtract`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4689-L4706)
---
## sum
```clojure
(sum input)
```
sum an array of images
Operation name: `sum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4708-L4724)
---
## svgload
```clojure
(svgload filename)
(svgload filename opts)
```
load SVG with rsvg
Operation name: `svgload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `dpi` -> `:dpi` (float): Render at this DPI
- `scale` -> `:scale` (float): Scale output by this factor
- `unlimited` -> `:unlimited` (boolean): Allow SVG of any size
- `stylesheet` -> `:stylesheet` (string): Custom CSS
- `high-bitdepth` -> `:high-bitdepth` (boolean): Enable scRGB 128-bit output (32-bit per channel)
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4726-L4755)
---
## switch
```clojure
(switch tests)
```
find the index of the first non-zero pixel in tests
Operation name: `switch`.
Returns an image handle.
Required inputs:
- `tests` -> `:tests` (seqable of image): Table of images to test
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4757-L4773)
---
## system
```clojure
(system cmd-format)
(system cmd-format opts)
```
run an external command
Operation name: `system`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `cmd-format` -> `:cmd-format` (string): Command to run
Optional inputs:
- `input` -> `:in` (seqable of image): Array of input images
- `in-format` -> `:in-format` (string): Format for input filename
- `out-format` -> `:out-format` (string): Format for output filename
- `cache` -> `:cache` (boolean): Cache this call
Outputs:
- `out` -> `:out` (image): Output image
- `log` -> `:log` (string): Command log
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4775-L4796)
---
## text
```clojure
(text text)
(text text opts)
```
make a text image
Operation name: `text`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `text` -> `:text` (string): Text to render
Optional inputs:
- `font` -> `:font` (string): Font to render with
- `width` -> `:width` (integer): Maximum image width in pixels
- `height` -> `:height` (integer): Maximum image height in pixels
- `align` -> `:align` (keyword, see `ol.vips.enums/align`): Align on the low, centre or high edge
- `justify` -> `:justify` (boolean): Justify lines
- `dpi` -> `:dpi` (integer): DPI to render at
- `spacing` -> `:spacing` (integer): Line spacing
- `fontfile` -> `:fontfile` (string): Load this font file
- `rgba` -> `:rgba` (boolean): Enable RGBA output
- `wrap` -> `:wrap` (keyword, see `ol.vips.enums/text-wrap`): Wrap lines on word or character boundaries
Outputs:
- `out` -> `:out` (image): Output image
- `autofit-dpi` -> `:autofit-dpi` (integer): DPI selected by autofit
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4798-L4825)
---
## thumbnail
```clojure
(thumbnail filename width)
(thumbnail filename width opts)
```
generate thumbnail from file
Operation name: `thumbnail`.
Returns an image handle.
Required inputs:
- `filename` -> `:filename` (string): Filename to read from
- `width` -> `:width` (integer): Size to this width
Optional inputs:
- `height` -> `:height` (integer): Size to this height
- `size` -> `:size` (keyword, see `ol.vips.enums/size`): Only upsize, only downsize, or both
- `no-rotate` -> `:no-rotate` (boolean): Don’t use orientation tags to rotate image upright
- `crop` -> `:crop` (keyword, see `ol.vips.enums/interesting`): Reduce to fill target rectangle, then crop
- `linear` -> `:linear` (boolean): Reduce in linear light
- `input-profile` -> `:input-profile` (string): Fallback input profile
- `output-profile` -> `:output-profile` (string): Fallback output profile
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `auto-rotate` -> `:auto-rotate` (boolean): Use orientation tags to rotate image upright
- `import-profile` -> `:import-profile` (string): Fallback import profile
- `export-profile` -> `:export-profile` (string): Fallback export profile
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4827-L4856)
---
## thumbnail-image
```clojure
(thumbnail-image input width)
(thumbnail-image input width opts)
```
generate thumbnail from image
Operation name: `thumbnail_image`.
Avoid for routine thumbnailing. Prefer `thumbnail` when you can start from a filename.For already-loaded images, use normal image operations such as `resize` unless you specifically need this escape hatch.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `width` -> `:width` (integer): Size to this width
Optional inputs:
- `height` -> `:height` (integer): Size to this height
- `size` -> `:size` (keyword, see `ol.vips.enums/size`): Only upsize, only downsize, or both
- `no-rotate` -> `:no-rotate` (boolean): Don’t use orientation tags to rotate image upright
- `crop` -> `:crop` (keyword, see `ol.vips.enums/interesting`): Reduce to fill target rectangle, then crop
- `linear` -> `:linear` (boolean): Reduce in linear light
- `input-profile` -> `:input-profile` (string): Fallback input profile
- `output-profile` -> `:output-profile` (string): Fallback output profile
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `auto-rotate` -> `:auto-rotate` (boolean): Use orientation tags to rotate image upright
- `import-profile` -> `:import-profile` (string): Fallback import profile
- `export-profile` -> `:export-profile` (string): Fallback export profile
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4858-L4889)
---
## tiffload
```clojure
(tiffload filename)
(tiffload filename opts)
```
load tiff from file
Operation name: `tiffload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `autorotate` -> `:autorotate` (boolean): Rotate image using orientation tag
- `subifd` -> `:subifd` (integer): Subifd index
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4891-L4920)
---
## tiffsave
```clojure
(tiffsave input filename)
(tiffsave input filename opts)
```
save image to tiff file
Operation name: `tiffsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `compression` -> `:compression` (keyword, see `ol.vips.enums/foreign-tiff-compression`): Compression for this file
- `q` -> `:Q` (integer): Q factor
- `predictor` -> `:predictor` (keyword, see `ol.vips.enums/foreign-tiff-predictor`): Compression prediction
- `tile` -> `:tile` (boolean): Write a tiled tiff
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `pyramid` -> `:pyramid` (boolean): Write a pyramidal tiff
- `miniswhite` -> `:miniswhite` (boolean): Use 0 for white in 1-bit images
- `bitdepth` -> `:bitdepth` (integer): Write as a 1, 2, 4 or 8 bit image
- `resunit` -> `:resunit` (keyword, see `ol.vips.enums/foreign-tiff-resunit`): Resolution unit
- `xres` -> `:xres` (float): Horizontal resolution in pixels/mm
- `yres` -> `:yres` (float): Vertical resolution in pixels/mm
- `bigtiff` -> `:bigtiff` (boolean): Write a bigtiff image
- `properties` -> `:properties` (boolean): Write a properties document to IMAGEDESCRIPTION
- `region-shrink` -> `:region-shrink` (keyword, see `ol.vips.enums/region-shrink`): Method to shrink regions
- `level` -> `:level` (integer): Deflate (1-9, default 6) or ZSTD (1-22, default 9) compression level
- `lossless` -> `:lossless` (boolean): Enable WEBP lossless mode
- `depth` -> `:depth` (keyword, see `ol.vips.enums/foreign-dz-depth`): Pyramid depth
- `subifd` -> `:subifd` (boolean): Save pyr layers as sub-IFDs
- `premultiply` -> `:premultiply` (boolean): Save with premultiplied alpha
- `rgbjpeg` -> `:rgbjpeg` (boolean): Output RGB JPEG rather than YCbCr
- `squash` -> `:squash` (boolean): Squash images down to 1 bit
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4922-L4966)
---
## tilecache
```clojure
(tilecache input)
(tilecache input opts)
```
cache an image as a set of tiles
Operation name: `tilecache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `max-tiles` -> `:max-tiles` (integer): Maximum number of tiles to cache
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
- `threaded` -> `:threaded` (boolean): Allow threaded access
- `persistent` -> `:persistent` (boolean): Keep cache between evaluations
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4968-L4990)
---
## tonelut
```clojure
(tonelut)
(tonelut opts)
```
build a look-up table
Operation name: `tonelut`.
Returns an image handle.
Required inputs:
- none
Optional inputs:
- `in-max` -> `:in-max` (integer): Size of LUT to build
- `out-max` -> `:out-max` (integer): Maximum value in output LUT
- `lb` -> `:Lb` (float): Lowest value in output
- `lw` -> `:Lw` (float): Highest value in output
- `ps` -> `:Ps` (float): Position of shadow
- `pm` -> `:Pm` (float): Position of mid-tones
- `ph` -> `:Ph` (float): Position of highlights
- `s` -> `:S` (float): Adjust shadows by this much
- `m` -> `:M` (float): Adjust mid-tones by this much
- `h` -> `:H` (float): Adjust highlights by this much
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L4992-L5018)
---
## transpose3d
```clojure
(transpose3d input)
(transpose3d input opts)
```
transpose3d an image
Operation name: `transpose3d`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `page-height` -> `:page-height` (integer): Height of each input page
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5020-L5037)
---
## uhdr2sc-rgb
```clojure
(uhdr2sc-rgb input)
```
transform uhdr to scRGB
Operation name: `uhdr2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5039-L5055)
---
## uhdrload
```clojure
(uhdrload filename)
(uhdrload filename opts)
```
load a UHDR image
Operation name: `uhdrload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5057-L5082)
---
## uhdrsave
```clojure
(uhdrsave input filename)
(uhdrsave input filename opts)
```
save image in UltraHDR format
Operation name: `uhdrsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `gainmap-scale-factor` -> `:gainmap-scale-factor` (integer): The scale factor of base image to gainmap image
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5084-L5108)
---
## unpremultiply
```clojure
(unpremultiply input)
(unpremultiply input opts)
```
unpremultiply image alpha
Operation name: `unpremultiply`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
- `alpha-band` -> `:alpha-band` (integer): Unpremultiply with this alpha
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5110-L5128)
---
## vipsload
```clojure
(vipsload filename)
(vipsload filename opts)
```
load vips from file
Operation name: `vipsload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5130-L5154)
---
## vipssave
```clojure
(vipssave input filename)
(vipssave input filename opts)
```
save image to file in vips format
Operation name: `vipssave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5156-L5178)
---
## webpload
```clojure
(webpload filename)
(webpload filename opts)
```
load webp from file
Operation name: `webpload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `scale` -> `:scale` (float): Factor to scale by
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5180-L5208)
---
## webpsave
```clojure
(webpsave input filename)
(webpsave input filename opts)
```
save as WebP
Operation name: `webpsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `exact` -> `:exact` (boolean): Preserve color values from transparent pixels
- `preset` -> `:preset` (keyword, see `ol.vips.enums/foreign-webp-preset`): Preset for lossy compression
- `smart-subsample` -> `:smart-subsample` (boolean): Enable high quality chroma subsampling
- `near-lossless` -> `:near-lossless` (boolean): Enable preprocessing in lossless mode (uses Q)
- `alpha-q` -> `:alpha-q` (integer): Change alpha plane fidelity for lossy compression
- `min-size` -> `:min-size` (boolean): Optimise for minimum size
- `kmin` -> `:kmin` (integer): Minimum number of frames between key frames
- `kmax` -> `:kmax` (integer): Maximum number of frames between key frames
- `effort` -> `:effort` (integer): Level of CPU effort to reduce file size
- `target-size` -> `:target-size` (integer): Desired target size in bytes
- `reduction-effort` -> `:reduction-effort` (integer): Level of CPU effort to reduce file size
- `mixed` -> `:mixed` (boolean): Allow mixed encoding (might reduce file size)
- `smart-deblock` -> `:smart-deblock` (boolean): Enable auto-adjusting of the deblocking filter
- `passes` -> `:passes` (integer): Number of entropy-analysis passes (in [1..10])
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5210-L5248)
---
## webpsave-mime
```clojure
(webpsave-mime input)
(webpsave-mime input opts)
```
save image to webp mime
Operation name: `webpsave_mime`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `exact` -> `:exact` (boolean): Preserve color values from transparent pixels
- `preset` -> `:preset` (keyword, see `ol.vips.enums/foreign-webp-preset`): Preset for lossy compression
- `smart-subsample` -> `:smart-subsample` (boolean): Enable high quality chroma subsampling
- `near-lossless` -> `:near-lossless` (boolean): Enable preprocessing in lossless mode (uses Q)
- `alpha-q` -> `:alpha-q` (integer): Change alpha plane fidelity for lossy compression
- `min-size` -> `:min-size` (boolean): Optimise for minimum size
- `kmin` -> `:kmin` (integer): Minimum number of frames between key frames
- `kmax` -> `:kmax` (integer): Maximum number of frames between key frames
- `effort` -> `:effort` (integer): Level of CPU effort to reduce file size
- `target-size` -> `:target-size` (integer): Desired target size in bytes
- `reduction-effort` -> `:reduction-effort` (integer): Level of CPU effort to reduce file size
- `mixed` -> `:mixed` (boolean): Allow mixed encoding (might reduce file size)
- `smart-deblock` -> `:smart-deblock` (boolean): Enable auto-adjusting of the deblocking filter
- `passes` -> `:passes` (integer): Number of entropy-analysis passes (in [1..10])
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5250-L5287)
---
## worley
```clojure
(worley width height)
(worley width height opts)
```
make a worley noise image
Operation name: `worley`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `cell-size` -> `:cell-size` (integer): Size of Worley cells
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5289-L5308)
---
## wrap
```clojure
(wrap input)
(wrap input opts)
```
wrap image origin
Operation name: `wrap`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `x` -> `:x` (integer): Left edge of input in output
- `y` -> `:y` (integer): Top edge of input in output
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5310-L5328)
---
## xyz
```clojure
(xyz width height)
(xyz width height opts)
```
make an image where pixel values are coordinates
Operation name: `xyz`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `csize` -> `:csize` (integer): Size of third dimension
- `dsize` -> `:dsize` (integer): Size of fourth dimension
- `esize` -> `:esize` (integer): Size of fifth dimension
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5330-L5350)
---
## xyz2-cmyk
```clojure
(xyz2-cmyk input)
```
transform XYZ to CMYK
Operation name: `XYZ2CMYK`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5352-L5368)
---
## xyz2-lab
```clojure
(xyz2-lab input)
(xyz2-lab input opts)
```
transform XYZ to Lab
Operation name: `XYZ2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `temp` -> `:temp` (seqable of number): Colour temperature
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5370-L5387)
---
## xyz2-oklab
```clojure
(xyz2-oklab input)
```
transform XYZ to Oklab
Operation name: `XYZ2Oklab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5389-L5405)
---
## xyz2-yxy
```clojure
(xyz2-yxy input)
```
transform XYZ to Yxy
Operation name: `XYZ2Yxy`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5407-L5423)
---
## xyz2sc-rgb
```clojure
(xyz2sc-rgb input)
```
transform XYZ to scRGB
Operation name: `XYZ2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5425-L5441)
---
## yxy2-xyz
```clojure
(yxy2-xyz input)
```
transform Yxy to XYZ
Operation name: `Yxy2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5443-L5459)
---
## zone
```clojure
(zone width height)
(zone width height opts)
```
make a zone plate
Operation name: `zone`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5461-L5479)
---
## zoom
```clojure
(zoom input xfac yfac)
```
zoom an image
Operation name: `zoom`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `xfac` -> `:xfac` (integer): Horizontal zoom factor
- `yfac` -> `:yfac` (integer): Vertical zoom factor
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips/operations.clj#L5481-L5499)
## ol.vips
# ol.vips
Load, transform, and write images with libvips from Clojure.
`ol.vips` exposes the runtime, I/O, metadata, and animated-image helpers that
most applications use directly. Loaders such as [`from-file`](#from-file),
[`from-buffer`](#from-buffer), and [`from-stream`](#from-stream) return closeable image handles.
Operations return new image handles rather than mutating the original image,
and sinks such as [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), and
[`write-to-stream`](#write-to-stream) trigger evaluation of the underlying libvips pipeline.
Guides and API reference live at https://docs.outskirtslabs.com/ol.vips/next/.
Use this namespace for:
* runtime initialization and safety controls
* file, buffer, and stream input/output
* metadata and raw header access
* animated-image metadata and frame-aware helpers
* low-level generic operation calls with [`call`](#call)
## Related Namespaces
* [`ol.vips.operations`](api/ol-vips-operations.adoc) for generated wrappers around the libvips operation surface
* [`ol.vips.enums`](api/ol-vips-enums.adoc) for normalized enum keywords and enum value sets
## Example
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/init!)
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300 {:auto-rotate true})
rotated (ops/rotate thumb 90.0)]
(v/write-to-file rotated "thumbnail.jpg")
(v/metadata rotated))
;; => {:width 300, :height 242, :bands 3, :has-alpha? false}
```
## init!
```clojure
(init!)
```
Starts libvips and loads the `ol.vips` native bindings for this process.
This function is idempotent. Most public API calls initialize libvips
lazily, so call `init!` when you want eager startup or want to inspect the
shared runtime state up front.
On first initialization, `ol.vips` restores the secure default and blocks
libvips operations marked as untrusted. See
[`set-block-untrusted-operations!`](#set-block-untrusted-operations!).
Returns the shared runtime state map, which should be considered an opaque handle and is not public API.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L47-L60)
---
## set-block-untrusted-operations!
```clojure
(set-block-untrusted-operations! blocked?)
```
Set whether libvips operations tagged as untrusted are blocked at runtime.
This is the direct wrapper around libvips `vips_block_untrusted_set`.
Pass `blocked?` as `true` to restore the secure default and block operations
libvips has marked as untrusted. Pass `false` to allow them to run.
Returns the current runtime state map.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L62-L71)
---
## set-operation-block!
```clojure
(set-operation-block! name blocked?)
```
Set the block state for a libvips operation class hierarchy.
`name` should be a libvips operation class name such as `"VipsForeignLoad"`
or `"VipsForeignLoadJpeg"`. libvips applies the block state at that point in
the class hierarchy and to all descendants. This is the direct wrapper around
libvips `vips_operation_block_set`.
Pass `blocked?` as `true` to block that class hierarchy, or `false` to allow
it.
Example:
```clojure
(v/set-operation-block! "VipsForeignLoad" true)
(v/set-operation-block! "VipsForeignLoadJpeg" false)
```
That blocks all loaders except the JPEG loader family.
Returns `{:name :blocked? }`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L73-L95)
---
## operation-cache-settings
```clojure
(operation-cache-settings)
```
Returns the current libvips operation cache settings.
Because libvips operations are free of side effects, libvips can cache a
previous call to the same operation with the same arguments and return the
previous result again.
Returns `{:max :size :max-mem :max-files }`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L97-L106)
---
## set-operation-cache-max!
```clojure
(set-operation-cache-max! max)
```
Sets the maximum number of operations libvips keeps in the operation cache.
Reducing this limit may trim cached operations immediately.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L108-L115)
---
## set-operation-cache-max-mem!
```clojure
(set-operation-cache-max-mem! max-mem)
```
Sets the maximum amount of tracked memory, in bytes, libvips allows before
it starts dropping cached operations.
libvips only tracks memory it allocates itself. Memory allocated by external
libraries is not included.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L117-L126)
---
## set-operation-cache-max-files!
```clojure
(set-operation-cache-max-files! max-files)
```
Sets the maximum number of tracked files libvips allows before it starts
dropping cached operations.
libvips only tracks file descriptors it opens itself. Descriptors opened by
external libraries are not included.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L128-L137)
---
## disable-operation-cache!
```clojure
(disable-operation-cache!)
```
Disables the libvips operation cache by setting the maximum cached
operation count to `0`.
This is often useful for image-proxy style workloads that process many
different images.
Returns the current cache settings map. See [`set-operation-cache-max!`](#set-operation-cache-max!).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L139-L148)
---
## tracked-resources
```clojure
(tracked-resources)
```
Returns the current libvips tracked resource counters.
libvips uses these counters to decide when to start dropping cached
operations. The returned map has these keys:
| | |
| --- | --- |
| key | description |
| `:mem` | Bytes currently allocated via libvips tracked allocators |
| `:mem-highwater` | Largest tracked allocation total seen so far |
| `:allocs` | Number of active tracked allocations |
| `:files` | Number of tracked open files |
These counters only include resources libvips tracks itself.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L150-L165)
---
## operations
```clojure
(operations)
```
Returns the sorted libvips operation nicknames known to the runtime.
These are the names accepted by [`call`](#call) and by [`operation-info`](#operation-info), such as
`"flip"`, `"rotate"`, or `"thumbnail_image"`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L167-L173)
---
## operation-info
```clojure
(operation-info operation-name)
```
Describes a libvips operation by nickname.
Returns a map with the operation `:name`, a short `:description`, and an
`:args` vector describing each argument’s name, blurb, type, and whether it
is an input, output, or required argument.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L175-L182)
---
## encode-enum
```clojure
(encode-enum enum-type-name value)
```
Encodes a Clojure enum value for a libvips enum type.
`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.
Pass a keyword like `:horizontal` or a valid enum integer value.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L184-L190)
---
## decode-enum
```clojure
(decode-enum enum-type-name value)
```
Decodes a libvips enum integer to a normalized Clojure keyword.
`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.
```clojure
(v/decode-enum "VipsDirection"
(v/encode-enum "VipsDirection" :horizontal))
;;=> :horizontal
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L192-L203)
---
## call
```clojure
(call operation-name opts)
```
Calls a libvips operation by nickname.
This is the low-level generic binding API described by libvips for language
bindings: create an operation from its nickname, set properties, execute with
the operation cache, then extract outputs.
`operation-name` should be a libvips operation nickname such as `"flip"` or
`"embed"`. `opts` keys should match libvips argument names as keywords.
Enum arguments accept the normalized keywords used by [`encode-enum`](#encode-enum). Image
arguments accept image handles and prior operation result maps with `:out`.
Returns the operation outputs in the most useful shape for Clojure:
* If the only output is an image at `:out`, returns that image handle.
* Otherwise returns a map of outputs.
* Returned output maps that hold closeable image values are themselves
closeable.
Example:
```clojure
(v/call "flip" {:in image
:direction :horizontal})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L205-L231)
---
## from-file
```clojure
(from-file path)
(from-file path opts)
```
Opens an image from `path` and returns a closeable image handle.
`path` can be a string, `java.nio.file.Path`, `java.io.File`, or another
`CharSequence`. Arity 2 appends `opts` in libvips option-string form. The
available option keys depend on the loader libvips selects for that path.
To discover loader-specific options like `:shrink`, see the generated
wrappers in [`ol.vips.operations`](api/ol-vips-operations.adoc), for example
[`ol.vips.operations/jpegload`](api/ol-vips-operations.adoc#jpegload) or [`ol.vips.operations/pngload`](api/ol-vips-operations.adoc#pngload). For enum
option values like `:sequential` on `:access`, see [`ol.vips.enums/access`](api/ol-vips-enums.adoc#access)
or [`ol.vips.enums/describe`](api/ol-vips-enums.adoc#describe).
```clojure
(v/from-file "input.jpg" {:access :sequential
:shrink 2})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L233-L253)
---
## write-to-file
```clojure
(write-to-file image path)
(write-to-file image path opts)
```
Writes `image` to `path` and returns `image`.
libvips infers the saver from the path or extension. Arity 3 appends
`opts` in libvips option-string form. The available option keys depend on
the saver libvips selects for that path.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave). For enum option values, see
[`ol.vips.enums`](api/ol-vips-enums.adoc).
```clojure
(v/write-to-file image "output.png" {:compression 9})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L255-L273)
---
## from-buffer
```clojure
(from-buffer source)
(from-buffer source opts)
```
Opens an image from in-memory bytes and returns a closeable image handle.
`source` may be a byte array or a byte sequence. Arity 2 passes `opts` as a
libvips option string for the loader.
The available option keys depend on the loader that recognizes the input
bytes. See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums`](api/ol-vips-enums.adoc) for enum value sets.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L275-L287)
---
## from-stream
```clojure
(from-stream is)
(from-stream is opts)
```
Opens an image from an `InputStream` and returns a closeable image handle.
The stream is bridged to a libvips source and is closed when the returned
image handle is closed. Arity 2 passes `opts` as a libvips option string for
the loader.
This is useful for streaming or non-file inputs, especially together with
loader hints like `:access :sequential`.
The available option keys depend on the loader that recognizes the stream.
See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums/access`](api/ol-vips-enums.adoc#access) for valid `:access` values.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L289-L305)
---
## write-to-buffer
```clojure
(write-to-buffer image suffix)
(write-to-buffer image suffix opts)
```
Encodes `image` and returns the result as a byte array.
`suffix` selects the saver and can include libvips save options directly,
such as `".png"` or `".png[compression=9]"`. Arity 3 appends `opts` to
`suffix` in libvips option-string form.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L307-L320)
---
## write-to-stream
```clojure
(write-to-stream image os suffix)
(write-to-stream image os suffix opts)
```
Encodes `image` to an `OutputStream` and returns `image`.
`suffix` selects the saver, for example `".png"` or `".jpg"`. Arity 4
appends `opts` to `suffix` in libvips option-string form.
The stream is flushed and closed after the write completes, and is also
closed if the write fails.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L322-L337)
---
## metadata
```clojure
(metadata image)
```
Returns a curated metadata map for `image`.
This is the high-level metadata view for the public API. It includes the core
image header fields such as width, height, bands, format, coding,
interpretation, resolution, offsets, and `:has-alpha?`, plus common animated
image fields when present such as `:pages`, `:page-height`, `:loop`, and
`:delay`.
For raw header and metadata access by libvips field name, see [`field`](#field),
[`field-names`](#field-names), and [`headers`](#headers).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L339-L351)
---
## field
```clojure
(field image field-name)
(field image field-name not-found)
```
Returns the libvips header or metadata field named by `field-name`.
Items of metadata are identified by strings. Use this for direct access to
libvips header fields and attached metadata such as `"xres"`,
`"icc-profile-data"`, or `"delay"`.
Arity 2 returns `nil` for missing fields. Arity 3 returns `not-found`
instead.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L353-L365)
---
## field-as-string
```clojure
(field-as-string image field-name)
(field-as-string image field-name not-found)
```
Returns the libvips field named by `field-name` rendered as a string.
This is the direct string form of a header or metadata field, useful for
display and debugging. Arity 3 returns `not-found` when the field is
absent.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L367-L376)
---
## field-names
```clojure
(field-names image)
```
Returns the libvips field names attached to `image`.
This includes both core header fields and attached metadata field names.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L378-L383)
---
## headers
```clojure
(headers image)
```
Returns the raw libvips header and metadata map for `image`.
Keys are the original libvips field names as strings. Prefer [`metadata`](#metadata)
when you want the higher-level normalized public view.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L385-L391)
---
## has-field?
```clojure
(has-field? image field-name)
```
Returns `true` if `image` has the libvips field named by `field-name`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L393-L396)
---
## width
```clojure
(width image)
```
Returns the image width in pixels.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L398-L401)
---
## height
```clojure
(height image)
```
Returns the image height in pixels.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L403-L406)
---
## bands
```clojure
(bands image)
```
Returns the number of image bands as an integer.
libvips images have three dimensions: width, height, and bands.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L408-L413)
---
## has-alpha?
```clojure
(has-alpha? image)
```
Returns `true` if `image` has an alpha band.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L415-L418)
---
## shape
```clojure
(shape image)
```
Returns `[width height bands]` for `image`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L420-L425)
---
## copy-memory
```clojure
(copy-memory image)
```
Materializes `image` into a private in-memory `VipsImage` and returns it as a
normal closeable image handle.
This is effectively a sink. Calling `copy-memory` forces libvips to evaluate
the current pipeline now, render the pixels into memory, and wrap that memory
in a new image handle. Unlike [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), or
[`write-to-stream`](#write-to-stream), the result is still an image you can keep using in
downstream image operations.
This is useful when an intermediate image will be reused several times and you
do not want libvips to recompute the upstream pipeline for each branch. It is
also the explicit way to ask for a private memory-backed image before applying
mutating draw operations.
Behavior:
* Preserves the rendered image pixels and image metadata.
* Returns a new handle that remains usable after the source pipeline handles
have been closed.
* May avoid an extra copy if libvips determines the input is already a simple
readable memory image, in which case it can return another reference to the
existing image instead of allocating again.
* Trades CPU savings for higher memory use, so it is best reserved for
intermediates you know are reused often enough to justify retaining the
pixels.
Example:
```clojure
(with-open [base (v/from-file "input.jpg")
step (-> base
(ops/resize 0.5)
(ops/sharpen))
cached (v/copy-memory step)]
(do-something cached)
(do-something-else cached))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L427-L466)
---
## assoc-field
```clojure
(assoc-field image field-name value)
(assoc-field image field-name value opts)
```
Returns a new image with the libvips field named by `field-name` set to `value`.
`assoc-field` is immutable at the API level: it leaves `image` unchanged and
returns a new image handle with the updated header or metadata field.
Arity 4 accepts an options map.
Options:
| | |
| --- | --- |
| key | description |
| `:type` | Explicit libvips field type to use when `value` is ambiguous, such as `:array-int` for integer vectors |
Header fields such as `"xres"` and `"yres"` are updated as real libvips
header values so they survive save and reload. Custom metadata fields are
copied onto a fresh image header and can be read back with [`field`](#field) or
[`headers`](#headers).
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (-> image
(v/assoc-field "xres" 10.0)
(v/assoc-field "delay" [10 20 30] {:type :array-int}))]
[(v/field tagged "xres")
(v/field tagged "delay")])
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L468-L500)
---
## update-field
```clojure
(update-field image field-name f & args)
```
Returns a new image with `field-name` updated by applying `f` to its current value.
This is the functional update variant of [`assoc-field`](#assoc-field). The current field
value is read with [`field`](#field) and passed to `f` along with any extra `args`.
The result becomes the new field value on the returned image.
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (v/assoc-field image "custom-int" 42)
updated (v/update-field tagged "custom-int" inc)]
(v/field updated "custom-int"))
;; => 43
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L502-L519)
---
## dissoc-field
```clojure
(dissoc-field image field-name)
```
Returns a new image with the libvips field named by `field-name` removed.
This leaves `image` unchanged and removes the field from a copied image
header. It is most useful for stripping attached metadata fields that should
not be preserved downstream.
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (v/assoc-field image "custom-string" "hello")
stripped (v/dissoc-field tagged "custom-string")]
(v/field stripped "custom-string"))
;; => nil
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L521-L538)
---
## pages
```clojure
(pages image)
```
Returns the animated page count from `image`, or `nil` when it is absent.
This reads the libvips `"n-pages"` field directly. For ordinary single-page
images that field is usually missing, so `pages` commonly returns `nil`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L540-L546)
---
## page-height
```clojure
(page-height image)
```
Returns the per-frame height for an animated image, or `nil` when it is absent.
libvips stores animated images as frames stacked vertically in one image.
`page-height` is the height of each logical frame.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L548-L554)
---
## page-delays
```clojure
(page-delays image)
```
Returns the animated frame delay vector from `image`, or `nil` when it is absent.
Delay values are returned as integers in the same order as the frames.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L556-L561)
---
## loop-count
```clojure
(loop-count image)
```
Returns the animated loop count from `image`, or `nil` when it is absent.
A loop count of `0` means loop forever when the target format supports that
convention.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L563-L569)
---
## assoc-pages
```clojure
(assoc-pages image page-count)
```
Returns a new image with animated page count metadata set to `page-count`.
`page-count` must be a positive integer. This updates the libvips
`"n-pages"` field on a copied image header and leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L571-L577)
---
## assoc-page-height
```clojure
(assoc-page-height image frame-height)
```
Returns a new image with animated frame height metadata set to `frame-height`.
`frame-height` must be a positive integer. This updates the libvips
`"page-height"` field on a copied image header and leaves `image`
unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L579-L586)
---
## assoc-page-delays
```clojure
(assoc-page-delays image delays)
```
Returns a new image with animated frame delay metadata set to `delays`.
`delays` must be a non-empty sequence of integers. When `image` already has
an explicit page count, the number of delay entries must match it.
This writes the libvips `"delay"` field with the correct array type and
leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L588-L597)
---
## assoc-loop-count
```clojure
(assoc-loop-count image loop-value)
```
Returns a new image with animated loop count metadata set to `loop-value`.
`loop-value` must be a non-negative integer. `0` means loop forever for
formats that use that convention. This leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L599-L605)
---
## extract-area-pages
```clojure
(extract-area-pages image left top width height)
```
Extracts the same rectangle from each frame of `image`.
For animated images, this crops every logical frame independently and returns
a reassembled animated image with `:pages`, `:page-height`, `:loop`, and
`:delay` preserved. For ordinary single-page images, this behaves like the
normal libvips `extract_area` operation.
Example:
```clojure
(with-open [image (ops/gifload "input.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)]
(select-keys (v/metadata cropped) [:width :height :pages :page-height]))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L607-L623)
---
## embed-pages
```clojure
(embed-pages image x y width height)
(embed-pages image x y width height opts)
```
Embeds each frame of `image` into a new canvas and returns the result.
For animated images, this applies libvips `embed` to every frame and then
reassembles the result while preserving animation metadata such as `:loop`
and `:delay`. For ordinary single-page images, this behaves like the normal
libvips `embed` operation.
Arity 6 accepts the same options map passed to libvips `embed`.
Options:
| | |
| --- | --- |
| key | description |
| `:extend` | How pixels outside the source image are filled, for example `:background` |
| `:background` | Background band values used when `:extend` is `:background` |
Example:
```clojure
(with-open [image (ops/gifload "input.gif" {:n -1})
framed (v/embed-pages image 8 8 70 70 {:extend :background
:background [0 0 0 0]})]
(v/page-height framed))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L625-L653)
---
## rot-pages
```clojure
(rot-pages image angle)
```
Rotates each frame of `image` by `angle`.
For animated images, this applies libvips `rot` to each frame and
reassembles the result with updated frame geometry and preserved animation
metadata. For ordinary single-page images, this behaves like the normal
libvips `rot` operation.
`angle` accepts the normalized enum keywords from [`ol.vips.enums/angle`](api/ol-vips-enums.adoc#angle),
such as `:d90`, `:d180`, or `:d270`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L655-L666)
---
## assemble-pages
```clojure
(assemble-pages frames)
(assemble-pages frames {:keys [loop delay] :as _opts})
```
Stacks `frames` into one animated image and annotates the result as multi-page.
`frames` must be a non-empty collection of images. Each input image becomes
one animation frame. All frames must have the same width and height. The
returned image has `:pages` set to the number of frames and `:page-height`
set to the height of each input frame.
Arity 2 accepts an options map.
Options:
| | |
| --- | --- |
| key | description |
| `:loop` | Animated loop count as a non-negative integer |
| `:delay` | Non-empty sequence of integer frame delays, one per frame |
`assemble-pages` requires at least one frame. When `:delay` is provided, its
entry count must match the number of frames.
Example:
```clojure
(with-open [animated (v/assemble-pages [frame-a frame-b frame-c]
{:loop 2
:delay [80 120 160]})]
(select-keys (v/metadata animated) [:pages :page-height :loop :delay]))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/heads/v0.0.1/src/ol/vips.clj#L668-L699)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## UNRELEASED
## `v0.0.1` (2026-07-13)
* Released the first public version of the `ol.vips` main library.
* Added a Clojure-owned FFI/FFM layer with generated operation and enum
wrappers, image I/O for files, byte arrays, and streams, metadata helpers,
and multipage and animated-image operations.
* Published native companion jars for Linux on x86-64 and AArch64 with glibc
or musl, macOS on x86-64 and Apple silicon, and Windows on x86-64.
* Updated every native companion jar to `sharp-libvips` 1.3.2, which bundles
libvips 8.18.3.
* Regenerated the operation and enum bindings for libvips 8.18.3, including
Oklab, Oklch, and UltraHDR support exposed by the upstream binaries.
* Added publication checks that validate every staged native bundle against
the project’s declared libvips version.
Please report any problems and let me know if anything is unclear or inconvenient. Thank you.
## Examples
# Examples
The snippets below show the main API shapes inline. For runnable
end-to-end scripts, see the examples in [`examples/`](https://github.com/outskirtslabs/vips/tree/main/examples):
* [`examples/create_thumbnail.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/create_thumbnail.clj) creates a thumbnail from a local file and writes it back out.
* [`examples/chain_transforms.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/chain_transforms.clj) chains several image operations together with the threading style API.
* [`examples/compose_images.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/compose_images.clj) joins two images and builds a small grid with `join` and `arrayjoin`.
* [`examples/bytes_and_streams.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/bytes_and_streams.clj) reads image bytes into memory, streams them through libvips, and writes the result back out.
* [`examples/http_stream_fetch.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/http_stream_fetch.clj) streams an HTTP response body into libvips and saves a transformed result.
* [`examples/image_diff.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/image_diff.clj) compares two same-sized images, thresholds the absolute difference, and composites a red highlight overlay onto the changed pixels.
* [`examples/palette_extractor.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/palette_extractor.clj) extracts the top dominant colors from an image with `hist_find_ndim`, prints channel stats from `stats`, and renders a swatch strip below the source image.
* [`examples/metadata_roundtrip.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/metadata_roundtrip.clj) reads and writes metadata fields before saving an image.
* [`examples/animated_gif.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/animated_gif.clj) loads every GIF frame, transforms each page, and writes an animated result.
## File Input And Output
Use the file helpers when your source and destination already live on
disk and you want libvips to infer formats from the path or suffix.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg" {:shrink 2})]
(v/metadata image)
(v/shape image)
(v/write-to-file image "rabbit.png" {:compression 9})
(v/write-to-buffer image ".png" {:compression 9}))
```
## Metadata
`ol.vips` exposes a curated `v/metadata` summary and a generic header
API for libvips metadata fields.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")]
{:meta (v/metadata image)
:width (v/field image "width")
:fields (take 5 (v/field-names image))})
;; => {:meta {:width 2490, :height 3084, :bands 3, :has-alpha? false}
;; :width 2490
;; :fields ("width" "height" "bands" "format" "coding")}
```
Immutable metadata edits use `assoc`/`update`/`dissoc` style names.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
tagged (-> image
(v/assoc-field "xres" 10.0)
(v/update-field "yres" (constantly 10.0)))]
(v/write-to-file tagged "rabbit-copy.jpg" {:strip false})
(select-keys (v/headers tagged) ["width" "height" "xres" "yres"]))
```
## Animated Images
Load every page or frame with a format-specific loader and
`{:n -1}`. libvips represents animated and multipage images as one
tall strip plus metadata such as `n-pages`, `page-height`, `loop`, and
`delay`.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)
turned (v/rot-pages cropped :d90)
looped (v/assoc-loop-count turned 2)]
(v/write-to-file looped "cogs-turned.gif")
(v/metadata looped))
;; => {:width 50, :height 250, :bands 4, :has-alpha? true, :pages 5, ...}
```
The dedicated animated helpers are:
* `v/pages`, `v/page-height`, `v/page-delays`, and `v/loop-count`
* `v/assoc-pages`, `v/assoc-page-height`, `v/assoc-page-delays`, and
`v/assoc-loop-count`
* `v/extract-area-pages`, `v/embed-pages`, `v/rot-pages`, and
`v/assemble-pages`
These helpers work best for uniform-height multipage inputs such as
animated GIF/WebP strips and similarly-shaped TIFF/PDF inputs.
For a deeper guide, see
[Multipage and Animated Images](https://docs.outskirtslabs.com/ol.vips/next/multipage-and-animated-images).
## Image Loaders
The generic loaders `v/from-file`, `v/from-buffer`, and `v/from-stream`
auto-detect the input format and accept option maps that libvips
forwards to the selected loader. When you need format-specific behavior,
use the generated loader operations in `ol.vips.operations`, such as
`ops/jpegload`.
`ol.vips` blocks libvips operations marked as untrusted by default. This can
block loaders with a larger attack surface, such as PDF or ImageMagick-backed
formats.
If you trust your input and need those operations, opt in explicitly
before loading that data:
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/set-block-untrusted-operations! false)
(with-open [image (ops/pdfload "document.pdf")]
(v/metadata image))
```
Use that override only for trusted inputs. See the upstream
https://www.libvips.org/API/8.18/func.block_untrusted_set.html[`vips_block_untrusted_set`
documentation]. In libvips terms this is equivalent to calling
`vips_block_untrusted_set(FALSE)` after initialization.
JPEG auto-rotation rotates the image according to its EXIF orientation
at load time:
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/jpegload "dev/rabbit.jpg" {:autorotate true})]
(v/write-to-file image "rabbit-upright.jpg")
(v/metadata image))
```
## Streaming Input And Output
`from-stream` reads from any `java.io.InputStream`. `from-buffer` reads
from a byte array already in memory.
```clojure
(require '[babashka.http-client :as http]
'[ol.vips :as v]
'[ol.vips.operations :as ops])
(import '[java.io FileOutputStream])
(with-open [response-body (:body (http/get "https://casey.link/square-flask.png"
{:as :stream}))
image (v/from-stream response-body {:access :sequential
:fail-on :error})
thumbnail (ops/thumbnail-image image 200)
out (FileOutputStream. "square-flask-thumb.png")]
(v/write-to-stream thumbnail out ".png")
(v/metadata thumbnail))
```
`write-to-stream` writes encoded bytes to any `java.io.OutputStream`.
```clojure
(require '[babashka.fs :as fs]
'[ol.vips :as v]
'[ol.vips.operations :as ops])
(import '[java.io FileOutputStream])
(let [source-bytes (fs/read-all-bytes "dev/rabbit.jpg")]
(with-open [image (v/from-buffer source-bytes)
thumbnail (ops/thumbnail-image image 200)
out (FileOutputStream. "rabbit-thumb.png")]
(v/write-to-stream thumbnail out ".png")
(v/metadata thumbnail)))
```
## Autorotate And Inspect Outputs
`autorot` returns a closeable result map. You can inspect `:angle` and
`:flip`, and still use the same value anywhere an image is expected.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
autorot (ops/autorot image)]
{:angle (:angle autorot)
:flip (:flip autorot)
:meta (v/metadata autorot)})
;; => {:angle :d0, :flip false, :meta {:width 2490, :height 3084, :bands 3, :has-alpha? false}}
```
## Resize And Crop
Use these geometry operations when you want to rescale an image first
and then work on a precise rectangular region.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
resized (ops/resize image 0.5)
cropped (ops/extract-area resized 100 100 500 500)]
(v/write-to-file resized "rabbit-resized.jpg")
(v/write-to-file cropped "rabbit-cropped.jpg")
{:resized (v/metadata resized)
:cropped (v/metadata cropped)})
```
## Smart Thumbnailing
`ops/thumbnail` exposes libvips’s higher-level thumbnail pipeline. Enum
options such as `:size` and `:crop` are documented in `ol.vips.enums`.
```clojure
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300
{:height 300
:size :down
:crop :attention})]
(v/write-to-file thumb "rabbit-smart-thumb.jpg")
(v/metadata thumb))
```
## Transforming Images
These operations are a good fit for orientation, color interpretation,
and other structural changes you want to compose into one pipeline.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
rotated (ops/rotate image 90.0)
flipped (ops/flip rotated :horizontal)
bw (ops/colourspace flipped :b-w)]
(v/write-to-file bw "rabbit-bw.jpg"))
```
## Filters And Effects
libvips includes a large set of convolution and enhancement operations,
so common blur and sharpen passes can stay inside the same image
pipeline.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
blurred (ops/gaussblur image 3.0)
sharp (ops/sharpen image {:sigma 1.0})]
(v/write-to-file blurred "rabbit-blur.jpg")
(v/write-to-file sharp "rabbit-sharp.jpg")
{:blurred (v/metadata blurred)
:sharp (v/metadata sharp)})
```
## Composing Images
Use the composition helpers when you need to join images, build contact
sheets, or align multiple sources into one output image.
```clojure
(with-open [left (v/from-file "dev/rabbit.jpg")
right (v/from-file "dev/rabbit.jpg")
joined (ops/join left right :horizontal)
grid (ops/arrayjoin [left right left right]
{:across 2
:shim 10
:halign :centre
:valign :centre})]
(v/write-to-file joined "rabbit-joined.jpg")
(v/write-to-file grid "rabbit-grid.jpg"))
```
## Web Optimization
The save helpers accept format-specific encoder options, which makes it
easy to produce smaller web-friendly JPEG and WebP outputs from the same
source image.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")]
(v/write-to-file image "rabbit-progressive.jpg"
{:interlace true
:strip true
:Q 85})
(v/write-to-file image "rabbit.webp"
{:Q 80
:effort 4}))
```
## Calling Raw Operations
Use `v/call` when you want access to the full libvips operation surface
before a dedicated convenience wrapper exists.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
rotated (v/call "rotate" {:in image :angle 90.0})]
(v/metadata rotated))
```
Use `v/operations` to list available libvips operations and
`v/operation-info` to inspect their inputs and outputs.
## Text Overlay
Generated operation wrappers also cover image synthesis and compositing,
so overlays like text labels can stay inside libvips rather than being
rendered in a separate tool.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
label (ops/text "ol.vips"
{:font "Sans Bold 48"
:rgba true})
poster (ops/composite2 image label :over {:x 40 :y 40})]
(v/write-to-file poster "rabbit-poster.png")
(v/metadata poster))
```
## Going to Production
# Going to Production
This page adapts the upstream libvips production checklist to `ol.vips`.
The original reference is the libvips
[Checklist for programmers using libvips](https://www.libvips.org/API/8.18/developer-checklist.html).
Use this page as guidance for application design and deployment. It is not a
hard rulebook, but these defaults are a good starting point for long-running
services, upload pipelines, and image proxy workloads.
## Prefer smart thumbnailing over load then resize
If you are starting from a filename and want a thumbnail, prefer
[`ops/thumbnail`](api/ol-vips-operations.adoc#thumbnail) over loading the
full image and then calling
[`ops/resize`](api/ol-vips-operations.adoc#resize).
Unlike a separate load followed by resize, `ops/thumbnail` combines loading
and shrinking into one step. That lets libvips use format-specific tricks such
as shrink-on-load, which can significantly reduce memory use and latency.
This is not only about speed. Thumbnailing at load time can also improve
quality, because libvips can premultiply automatically where needed and can
render vector inputs at the target size instead of rasterizing them large and
shrinking afterwards.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [thumb (ops/thumbnail "input.jpg" 300 {:height 300})]
(v/write-to-file thumb "thumb.jpg"))
```
If you already have an image handle in hand, there is still a lower-level
[`ops/thumbnail-image`](api/ol-vips-operations.adoc#thumbnail-image)
wrapper. Treat it as an escape hatch. It wraps libvips `thumbnail_image`, so
it does not get the load-time rendering and shrink-on-load advantages that
`ops/thumbnail` gets from starting at the source.
## Use sequential access when your pipeline can stream
When you know you will read the source once in loader order, pass
`{:access :sequential}` to your loader. This often reduces memory use and can
improve throughput.
For background on why this matters, see the upstream libvips
https://www.libvips.org/API/8.18/how-it-opens-files.html["How it opens files"
chapter].
```clojure
(with-open [image (v/from-file "input.jpg" {:access :sequential})]
(v/metadata image))
```
The same applies to [`v/from-stream`](api/ol-vips.adoc#from-stream),
[`v/from-buffer`](api/ol-vips.adoc#from-buffer), and format-specific
loader operations such as
[`ops/jpegload`](api/ol-vips-operations.adoc#jpegload) and
[`ops/pngload`](api/ol-vips-operations.adoc#pngload).
## Prefer longer pipelines to many materialized steps
libvips is demand-driven and uses partial images as intermediates. In
`ol.vips`, that means you can usually build one longer pipeline of operations
without materializing every step in memory or on disk. The work is deferred
until a sink asks for pixels, such as
[`v/write-to-file`](api/ol-vips.adoc#write-to-file),
[`v/write-to-buffer`](api/ol-vips.adoc#write-to-buffer), or
[`v/write-to-stream`](api/ol-vips.adoc#write-to-stream).
This is one of the main reasons to lean into the libvips model instead of
breaking processing into many disconnected phases. Long pipelines usually stay
memory-efficient because intermediate results are represented as graph nodes
rather than fully realized images.
libvips is also horizontally threaded: threads tend to run along the pipeline
you are evaluating, rather than up and down the image. In practice, that means
longer pipelines often parallelize better than shorter ones.
```clojure
(with-open [image (v/from-file "input.jpg")
result (-> image
(ops/resize 0.5)
(ops/colourspace :b-w)
(ops/sharpen))]
(v/write-to-file result "output.jpg"))
```
If you can, aim for one coherent processing pipeline per output rather than a
series of intermediate writes, reloads, and separate mini-pipelines.
This generally works better than repeatedly writing intermediates to disk or
breaking the flow into many disconnected steps.
## Reuse shared intermediates intentionally
If one derived image is reused several times in the same request, bind it once
and pass that handle to downstream operations instead of recalculating it.
```clojure
(with-open [image (v/from-file "input.jpg")
base (ops/resize image 0.5)
preview (ops/colourspace base :srgb)
mask (ops/extract-band base 0)]
...)
```
If you need to materialize a reused intermediate so libvips does not
recalculate it, use [`v/copy-memory`](api/ol-vips.adoc#copy-memory). This
forces the current pipeline to render into a private in-memory image and
returns another image handle you can fan out to several downstream operations
or keep in an application cache for later reuse.
This can trade CPU time for higher memory use, so it is best reserved for
intermediates you know are reused often enough to justify the retained pixels.
## Put large resizes early in the pipeline
If a pipeline includes a large resize, do it near the start. After that, apply
area operations such as sharpening, then point operations.
This reduces the amount of pixel data that later stages need to touch.
## Restrict loaders and untrusted inputs
`ol.vips` starts from a secure default: libvips operations marked as
untrusted are blocked during initialization. If you handle untrusted data,
keep
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-)
set to `true` unless you have a specific trusted-input path that needs those
loaders.
If you need direct control over that default, use
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-).
Passing `true` restores the default secure posture, and passing `false`
allows operations libvips has marked as untrusted.
libvips also has a lower-level class-hierarchy blocker, exposed as
[`v/set-operation-block!`](api/ol-vips.adoc#set-operation-block-BANG-).
This lets you block broad families of operations and selectively re-enable the
subset you trust. For example, to allow only JPEG loaders:
```clojure
(require '[ol.vips :as v])
(v/set-block-untrusted-operations! true)
(v/set-operation-block! "VipsForeignLoad" true)
(v/set-operation-block! "VipsForeignLoadJpeg" false)
```
After those calls, libvips will only load JPEGs from the foreign-loader
hierarchy. This is useful when you need tighter runtime control without
building a custom libvips binary.
If you need tighter control over which loaders are present at all, prefer
shipping a custom libvips build and loading it with `-Dol.vips.native.preload`
instead of relying on a broader system installation.
See also: [Security Policy](security.adoc).
## Sanity-check images before expensive processing
Open the image, inspect cheap metadata first, and reject inputs that are too
large or unsuitable for your main pipeline.
```clojure
(with-open [image (v/from-file "upload.jpg" {:access :sequential})]
(let [width (v/width image)
height (v/height image)
interlaced (v/field image "interlaced" false)]
(when (> (* width height) 100000000)
(throw (ex-info "image too large" {:width width :height height})))
(when interlaced
(throw (ex-info "progressive images are not allowed" {})))))
```
This is especially useful for defending against decompression bombs and for
keeping progressive or interlaced inputs away from latency-sensitive paths.
In practice that usually means checking
[`v/width`](api/ol-vips.adoc#width),
[`v/height`](api/ol-vips.adoc#height), and
[`v/field`](api/ol-vips.adoc#field) before you start expensive work.
## Tune the Linux allocator for long-running glibc services
On glibc-based Linux systems, long-running multithreaded image workloads can
benefit from an alternative allocator such as
[jemalloc](https://github.com/jemalloc/jemalloc). The default allocator often
performs poorly for long-running, multithreaded processes with frequent small
allocations, and switching allocators can reduce the off-heap footprint of the
JVM when using libvips.
This is an operating environment concern rather than an `ol.vips` API setting.
On Linux that usually means configuring `LD_PRELOAD` before launching the JVM,
not changing anything inside your image pipeline.
The jemalloc project is also in a somewhat unsettled state. See
[this postmortem](https://jasone.github.io/2025/06/12/jemalloc-postmortem/) for
background, and [Facebook’s fork](https://github.com/facebook/jemalloc) for one
possible continuation point.
Musl-based Linux systems and non-Linux runtimes are generally less affected by
this specific issue.
## Disable or tune the libvips operation cache for proxy workloads
For image proxy workloads that process many unrelated images, the libvips
operation cache is often not useful. Disable it entirely with
[`v/disable-operation-cache!`](api/ol-vips.adoc#disable-operation-cache-BANG-):
```clojure
(require '[ol.vips :as v])
(v/init!)
(v/disable-operation-cache!)
```
The cache is more useful when the same or very similar operation graphs are
reused repeatedly in one process, for example a service that applies a small
set of common transforms over and over. In that kind of workload, the defaults
may be fine and you may not want to touch them.
When you do need to tune it, think about the three limits separately:
* `v/set-operation-cache-max!` limits how many recent operations libvips keeps.
Lower this when cache churn is high and you are not seeing reuse. Raise it
only if you have evidence that repeated operation graphs are being evicted too
aggressively.
* `v/set-operation-cache-max-mem!` limits how much libvips-tracked memory can
accumulate before cached operations start getting dropped. Lower this when
you want cache eviction to happen earlier under memory pressure.
* `v/set-operation-cache-max-files!` limits how many libvips-tracked file
descriptors can accumulate before cached operations start getting dropped.
Lower this when file handle pressure matters more than cache hit rate.
Tune the cache limits directly like this:
```clojure
(v/set-operation-cache-max! 0)
(v/set-operation-cache-max-mem! (* 32 1024 1024))
(v/set-operation-cache-max-files! 32)
(v/operation-cache-settings)
;; => {:max 0, :size 0, :max-mem 33554432, :max-files 32}
(v/tracked-resources)
;; => {:mem 0, :mem-highwater 0, :allocs 0, :files 0}
```
[`v/operation-cache-settings`](api/ol-vips.adoc#operation-cache-settings)
reports the current libvips cache limits and the current cache size.
[`v/tracked-resources`](api/ol-vips.adoc#tracked-resources) reports
libvips tracked memory, highwater memory, allocation count, and tracked file
count.
Those tracked counters are useful for observing trends, but they only include
resources libvips tracks itself. Memory or file descriptors used inside
external libraries may not be reflected there, so treat them as a lower bound
rather than a complete process-wide accounting.
## Ship only the native bundle you need
This is an `ol.vips`-specific deployment concern. The main library is small,
but the companion native jars contain the bundled libvips binaries and their
support libraries for a specific platform. Those jars are usually the largest
part of an `ol.vips` deployment artifact.
If you know the production target in advance, prefer packaging only the one
native companion jar that matches that runtime platform. For example, a Linux
x86-64 glibc deployment usually only needs
`com.outskirtslabs/vips-native-linux-x86-64-gnu` on the classpath.
Including several platform jars is useful for development, shared tooling, or
distributing a generic application bundle. But for a single-platform deploy it
usually just makes the image, container layer, or classpath larger without
adding runtime value, since `ol.vips` will only load the bundle that matches
the detected target platform.
If possible, build separate deployment artifacts per target platform and keep
each one trimmed to the matching native dependency set.
## ol.vips
# ol.vips
> Clojure bindings for [libvips](https://github.com/libvips/libvips) image
> processing library



`ol.vips` is a Clojure library for loading, transforming, and saving
images with [libvips](https://github.com/libvips/libvips). Use it to
build thumbnails and responsive image variants, convert formats, crop
and resize uploads, compose images, read and write metadata, and run
image pipelines from files, byte arrays, or streams.
If you have never used libvips before: it is a native image processing
library designed for fast, memory-efficient image work. `ol.vips` gives
Clojure programs access to that engine with a Clojure-first API,
generated wrappers for the full libvips operation surface, and
platform-native jars so you can use libvips from the JVM without
building the native integration yourself.
`ol.vips` uses [coffi](https://github.com/IGJoshua/coffi) and requires Java
22{plus} for FFI/FFM (Project Panama).
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
It is used in production, but needs more exercise and feedback from the community before I commit to a stable version.
## Installation
Install the main library plus exactly one native jar that matches your
target platform.
```clojure
;; deps.edn
{:deps {com.outskirtslabs/vips {:mvn/version "0.0.1"}
;; change the following based on your runtime platform
com.outskirtslabs/vips-native-linux-x86-64-gnu {:mvn/version "1.3.2-1"}}}
```
https://clojars.org/search?q=group-id%3Acom.outskirtslabs+AND+artifact-id%3Avips-native*[Choose
one or more native artifacts] (only the appropriate one for the runtime
platform will be used):
* `com.outskirtslabs/vips-native-linux-x86-64-gnu`
* `com.outskirtslabs/vips-native-linux-x86-64-musl`
* `com.outskirtslabs/vips-native-linux-aarch64-gnu`
* `com.outskirtslabs/vips-native-linux-aarch64-musl`
* `com.outskirtslabs/vips-native-macos-x86-64`
* `com.outskirtslabs/vips-native-macos-aarch64`
* `com.outskirtslabs/vips-native-win32-x86-64`
The native companion jars bundle libvips 8.18.3 from `sharp-libvips` 1.3.2.
The test suite runs against that bundle.
`ol.vips` requires Java 22{plus} and native access enabled for the JVM
process:
```clojure
;; deps.edn
{:aliases
{:dev {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
```
To use your own libvips build, see
[Loading the native library](https://docs.outskirtslabs.com/ol.vips/next/introduction#loading_the_native_library).
## Quick Start
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/init!)
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300 {:auto-rotate true})
rotated (ops/rotate thumb 90.0)]
(v/write-to-file rotated "thumbnail.jpg")
(v/metadata rotated))
;; => {:width 300, :height 242, :bands 3, :has-alpha? false}
```
The top-level `ol.vips` namespace provides the image loading, saving,
metadata, and convenience helpers. `ol.vips.operations` contains
generated wrappers for libvips operations.
## Documentation
* [Docs Home](https://docs.outskirtslabs.com/ol.vips/next/)
* [Introduction](https://docs.outskirtslabs.com/ol.vips/next/introduction/)
* [Examples](https://docs.outskirtslabs.com/ol.vips/next/examples/)
* [Going to Production](https://docs.outskirtslabs.com/ol.vips/next/going-to-production/)
* [Multipage and Animated Images](https://docs.outskirtslabs.com/ol.vips/next/multipage-and-animated-images/)
* [API Reference](https://docs.outskirtslabs.com/ol.vips/next/api/ol-vips)
* [Changelog](https://docs.outskirtslabs.com/ol.vips/next/changelog/)
* [Security Policy](https://docs.outskirtslabs.com/ol.vips/next/security/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/vips/issues)
## Licensing
The Clojure library `ol.vips` is copyright (C) 2026 Casey Link and is
licensed under [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
The platform-native companion jars under `native/` redistribute upstream
[sharp-libvips](https://github.com/lovell/sharp-libvips/releases) binary
bundles. Those redistributed native binaries are licensed separately
from the `ol.vips` source, principally under LGPL-3.0-or-later.
## Introduction
# Introduction
`ol.vips` wraps libvips’s image graph in
[Closeable](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Closeable.html)
Clojure values. Loader functions such as `v/from-file`, `v/from-buffer`,
and `v/from-stream` return image handles, and operations such as
`ops/resize`, `ops/colourspace`, and `v/call` return new handles rather
than mutating the original image.
This model is immutable and pipeline-oriented. In the common case,
libvips reads only enough of the input to discover image metadata and
build an operation graph. Calling loaders and transforms usually does
not decode the whole image up front.
libvips is demand-driven, so the pixel work happens when something
downstream asks for image data. In `ol.vips`, that usually means a sink
such as `v/write-to-file`, `v/write-to-buffer`, or `v/write-to-stream`.
At that point libvips pulls pixels through the pipeline, processing
small regions with a horizontally threaded execution model instead of
materializing every intermediate image in memory.
Some operations that need actual pixel values can trigger evaluation
earlier, but metadata queries such as `v/width`, `v/height`, and
`v/metadata` can often be satisfied from loader metadata alone. Because
the image graph is immutable and operations are generally side-effect
free, libvips can also reuse and cache operation results internally.
`ol.vips` adds a small amount of Clojure-specific structure on top of
that model. The `ol.vips.operations` namespace is generated from libvips
introspection data, `ol.vips.enums` exposes the enum keywords used by
many options, and the top-level `ol.vips` namespace provides the file,
buffer, stream, metadata, and convenience helpers you use most often.
## Why libvips?
The typical libvips workflow is exactly what many applications need:
load a large source image, resize or transform it, and write a smaller
web-friendly JPEG, PNG, WebP, AVIF, or TIFF result. `ol.vips` gives
Clojure access to that model without shelling out to external image
tools or spawning child processes.
libvips is
https://github.com/libvips/libvips/wiki/Why-is-libvips-quick[blazing
fast] and
https://github.com/libvips/libvips/wiki/Speed-and-memory-use[memory
efficient] because it usually works on small regions of uncompressed
image data at a time, takes advantage of multiple CPU cores, and avoids
materializing every intermediate result in memory. That execution model
is a strong fit for image pipelines, thumbnails, upload processing,
preprocessing for vision workloads, and other server-side image tasks.
It also brings broad format and operation support:
[300 operations](https://www.libvips.org/API/current/func-list.html)
covering arithmetic, histograms, convolution, morphology, colour,
resampling, statistics, and more, with correct handling of colour
spaces, embedded ICC profiles, and alpha channels.
[libvips](https://github.com/libvips/libvips) was originally created at
Birkbeck College and is currently maintained by
[John Cupitt](https://github.com/jcupitt) and other contributors.
### Bindings Generation
libvips itself is very amenable to automatic bindings generation. The
core of `ol.vips` is small introspection layer that loads the native
library then introspects it to generate `ol.vips.operations` and
`ol.vips.enums`, and then a small runtime for loading/reading/writing.
As a convenience for Clojure users there are separate jars available
with the native libraries per-platform, but `ol.vips` it self does not
depend on them directly. You can depend on one (or more) of those native
deps, or provide your own libvips build with `-Dol.vips.native.preload`.
You’ll need to choose one.
The published native companion jars bundle libvips 8.18.3 from
`sharp-libvips` 1.3.2. The test suite runs against that bundle.
This means that you can even use a custom libvips build or a newer
version of libvips without having to wait for `ol.vips` to catch up
(though I expect to keep it up to date). Any new or updated operations
in libvips can be used with this library using the lower level
`ol.vips/call` function. You can even generate the bindings yourself if
you need to, simply use `ol.vips.codegen`.
## Going to Production
See [Going to Production](https://docs.outskirtslabs.com/ol.vips/next/going-to-production)
for production guidance on pipeline shape, sequential access, untrusted
inputs, sanity checks, allocator tuning, and cache settings in `ol.vips`.
See [Examples](examples.adoc)
for runnable snippets that cover file, buffer, stream, metadata,
thumbnailing, transforms, composition, and animated-image workflows.
## Loading the native library
`ol.vips` uses this procedure to load the native components:
1. Load any explicit libraries listed in `-Dol.vips.native.preload`.
2. Load the extracted libraries from the platform native jar on the
classpath.
3. If that fails, fall back to the system library loader, which can
resolve `libvips` from `LD_LIBRARY_PATH`.
### 1. Preload
`-Dol.vips.native.preload` is for exact native library file paths. Use
it when you want to point `ol.vips` at a specific custom build in a
non-standard location, or when you need to preload dependency libraries
before libvips itself. On Nix-like systems that can include things like
`libstdc{plus}{plus}.so.6` as well as your `libvips` library. The value
is a single string containing one or more full library file paths
separated by the OS path separator, which is `:` on Linux and macOS and
`;` on Windows.
If `ol.vips.native.preload` is set, those entries are always loaded
first. If any preload entry fails to load, the packaged native-jar path
is abandoned for that initialization attempt and `ol.vips` falls back to
the system library loader instead of continuing with the native jar.
### 2. Classpath native bundle
`ol.vips` detects the current platform, builds a resource path like
`ol/vips/native/-` on macOS and Windows or
`ol/vips/native/--` on Linux, and then looks
for `manifest.edn` under that path.
For example, Linux x86-64 glibc resolves to
`ol/vips/native/linux-x86-64-gnu/manifest.edn`. If that resource is
present, `ol.vips` reads the manifest, extracts the bundled native
libraries into the local cache, and loads those extracted files.
You can override parts of that platform detection with
`-Dol.vips.native.platform-id`, `-Dol.vips.native.os`,
`-Dol.vips.native.arch`, and `-Dol.vips.native.libc`.
In practice, this means the companion jar for the current runtime
platform needs to be on the classpath, meaning the current OS, CPU
architecture, and Linux libc when applicable. This path is attempted
after any explicit `ol.vips.native.preload` entries.
In the successful packaged case, `v/init!` will report
`:native-load-source :packaged` and expose the extracted primary library
path in `:primary-library-path`, which is useful when debugging exactly
what got loaded.
### 3. System fallback
The system-library fallback loads by library name rather than full file
path. Use this when you want the OS or JVM loader to resolve libvips
from `LD_LIBRARY_PATH`, standard system locations, or other
platform-specific loader configuration. A platform native jar is not
required for this path; if the packaged load fails, including because no
matching native jar is present on the classpath, `ol.vips` can still
fall back to the system loader.
By default the system fallback tries `vips-cpp` and then `vips`. Most
users should not need to change this. `-Dol.vips.native.system-libs`
exists for the uncommon case where your environment exposes libvips
under different system library names. Like `ol.vips.native.preload`, it
accepts multiple entries separated by the OS path separator.
The fallback only applies to native library loading. If the packaged
libraries load successfully but `vips_init` fails afterwards,
`ol.vips` does not automatically retry via the system loader. For
debugging, `v/init!` exposes runtime state including whether
initialization came from the packaged path or the system fallback.
## Multipage and Animated Images
# Multipage and Animated Images
This page adapts the upstream libvips guide for `ol.vips`. The original
reference is
[Multipage and animated images](https://www.libvips.org/API/8.18/multipage-and-animated-images.html).
libvips represents animated and multipage images as one tall image plus
metadata describing how that strip should be interpreted. In `ol.vips`, the
main metadata fields are
[`v/pages`](api/ol-vips.adoc#pages),
[`v/page-height`](api/ol-vips.adoc#page-height),
[`v/page-delays`](api/ol-vips.adoc#page-delays), and
[`v/loop-count`](api/ol-vips.adoc#loop-count).
This model works best when every page or frame has the same dimensions. That
is common for animated GIF and WebP, and also for some TIFF or PDF inputs. If
page sizes vary, read and process them one at a time instead of treating the
file as one uniform strip.
## Reading multipage images
By default, libvips usually reads only the first page or frame. Pass `{:n -1}`
to load every page, or use `{:page ... :n ...}` to load a single page or a
range.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})]
(select-keys (v/metadata image)
[:width :height :pages :page-height :loop :delay]))
;; => {:width 85
;; :height 385
;; :pages 5
;; :page-height 77
;; :loop 32761
;; :delay [0 50 50 50 50]}
```
Points to note:
* [`ops/gifload`](api/ol-vips-operations.adoc#gifload) with `{:n -1}` loads
every frame in the animation.
* `:page-height` is the height of one frame within the strip.
* `:pages * :page-height == :height` for uniform-height images.
* `:loop` is the loop count for animated formats.
* `:delay` is a vector of frame delays in milliseconds.
You can also load a subset of frames:
```clojure
(with-open [two-frames (ops/gifload "test/fixtures/cogs.gif" {:page 2 :n 2})]
(select-keys (v/metadata two-frames) [:height :pages :page-height]))
```
Be careful interpreting metadata on partial loads. The loaded strip height
reflects the selected range, but some loaders can still preserve source
metadata such as the original total `n-pages`. For frame slicing logic, rely on
the loaded image dimensions together with `page-height`, not only on the
reported source page count.
The same loader options apply to other multipage-capable loaders such as
[`ops/webpload`](api/ol-vips-operations.adoc#webpload) and
[`ops/tiffload`](api/ol-vips-operations.adoc#tiffload), or to the generic
[`v/from-file`](api/ol-vips.adoc#from-file) path when you want format
autodetection.
For PDF input, remember that `ol.vips` blocks libvips operations marked as
untrusted during initialization. If you have a trusted PDF input path and need
to load it, opt in first with
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-)
set to `false`.
## Writing multipage images
If an image already has the right strip layout and metadata, writing an
animated or multipage image is just a normal save. For example, you can load a
GIF animation and write it back as animated WebP:
```clojure
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})]
(v/write-to-file image "cogs.webp"))
```
That works because the loaded image already carries the `n-pages`,
`page-height`, `loop`, and `delay` metadata that the saver needs.
The same pattern applies to multipage TIFF output with
[`ops/tiffsave`](api/ol-vips-operations.adoc#tiffsave) or
to generic saves through [`v/write-to-file`](api/ol-vips.adoc#write-to-file)
when libvips can infer the target format from the filename.
## Building an animation from frames
The upstream libvips examples build a vertical strip and then set metadata by
hand. In `ol.vips`, prefer
[`v/assemble-pages`](api/ol-vips.adoc#assemble-pages) when you already have
a sequence of equal-sized frames. It joins the frames into one strip and sets
the page metadata for you.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [base (v/from-file "test/fixtures/puppies.jpg")
frame-a (ops/extract-area base 0 0 40 30)
frame-b (ops/extract-area base 10 10 40 30)
frame-c (ops/extract-area base 20 20 40 30)
animated (v/assemble-pages [frame-a frame-b frame-c]
{:loop 2
:delay [80 120 160]})]
(v/write-to-file animated "puppies.gif")
(select-keys (v/metadata animated)
[:width :height :pages :page-height :loop :delay]))
```
Here, `frame-a`, `frame-b`, and `frame-c` are just ordinary image handles.
`v/assemble-pages` does not require a special frame type. The `frames`
argument is simply a non-empty collection of same-sized images, with each image
becoming one animation frame in the output.
In practice, those images can come from any image-producing operation, not only
from [`ops/extract-area`](api/ol-vips-operations.adoc#extract-area). You
can resize, crop, composite, render text, or load separate files first, then
pass the resulting image handles to `v/assemble-pages`.
If you already have a tall strip and need to annotate or adjust it, use
[`v/assoc-pages`](api/ol-vips.adoc#assoc-pages),
[`v/assoc-page-height`](api/ol-vips.adoc#assoc-page-height),
[`v/assoc-page-delays`](api/ol-vips.adoc#assoc-page-delays), and
[`v/assoc-loop-count`](api/ol-vips.adoc#assoc-loop-count).
## Applying operations page by page
For uniform-height multipage images, the page-aware helpers apply ordinary
operations frame by frame and keep the metadata consistent.
```clojure
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)
turned (v/rot-pages cropped :d90)
looped (v/assoc-loop-count turned 2)]
(v/write-to-file looped "cogs-turned.gif")
(select-keys (v/metadata looped)
[:width :height :pages :page-height :loop :delay]))
```
The main helpers are:
* [`v/extract-area-pages`](api/ol-vips.adoc#extract-area-pages)
* [`v/embed-pages`](api/ol-vips.adoc#embed-pages)
* [`v/rot-pages`](api/ol-vips.adoc#rot-pages)
* [`v/assemble-pages`](api/ol-vips.adoc#assemble-pages)
For single-page inputs, these helpers fall back to the ordinary single-image
behavior. For true multipage work, they assume that the image can be divided
cleanly into equal-height pages.
## Security
# Security
Please report vulnerabilities through [GitHub Security Advisories](https://github.com/outskirtslabs/vips/security/advisories).
For general policy and support expectations, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
## ol.vips.codegen
# ol.vips.codegen
Generate the introspected wrapper namespaces for `ol.vips`.
This namespace queries the live libvips runtime through [`ol.vips`](api/ol-vips.adoc) and
`ol.vips.impl.introspect`, then writes the generated public wrapper files:
* `src/ol/vips/enums.clj`
* `src/ol/vips/operations.clj`
The generated files are derived from the currently loaded libvips build, so
run this from a development environment where `ol.vips` can initialize its
native libraries.
Usage:
```clojure
(require '[ol.vips.codegen :as codegen])
(codegen/generate!)
;;=> {:enum-count 42
;; :operation-count 233
;; :skipped-count 57
;; :paths ["src/ol/vips/enums.clj"
;; "src/ol/vips/operations.clj"]}
```
In the ol.vips code repo the babashka task `bb codegen` is the normal project entry point for this
namespace.
## generate!
```clojure
(generate!)
```
Generates `ol.vips.enums` and `ol.vips.operations` from live libvips introspection.
Initializes the runtime if needed, discovers the supported operations and
enums from the current libvips build, writes the generated source files, and
returns a summary map describing what was emitted.
Example:
```clojure
(generate!)
;;=> {:enum-count ...
;; :operation-count ...
;; :skipped-count ...
;; :paths ["src/ol/vips/enums.clj" "src/ol/vips/operations.clj"]}
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/codegen.clj#L441-L481)
---
## -main
```clojure
(-main & _args)
```
Runs [`generate!`](#generate!) and prints a short CLI summary.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/codegen.clj#L483-L490)
## ol.vips.enums
# ol.vips.enums
Generated enum descriptors for normalized libvips enum ids.
Use [`registry`](#registry) or the generated enum vars to inspect enum metadata. Use [`ol.vips/encode-enum`](api/ol-vips.adoc#encode-enum) and [`ol.vips/decode-enum`](api/ol-vips.adoc#decode-enum) to convert between keywords and the underlying libvips integer values.
## registry
Generated registry of libvips enum descriptors keyed by normalized enum id.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L11-L54)
---
## access
Generated enum descriptor for `VipsAccess`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:random`
- `:sequential`
- `:sequential-unbuffered`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L56-L65)
---
## align
Generated enum descriptor for `VipsAlign`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:centre`
- `:high`
- `:low`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L67-L76)
---
## angle
Generated enum descriptor for `VipsAngle`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:d0`
- `:d180`
- `:d270`
- `:d90`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L78-L88)
---
## angle45
Generated enum descriptor for `VipsAngle45`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:d0`
- `:d135`
- `:d180`
- `:d225`
- `:d270`
- `:d315`
- `:d45`
- `:d90`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L90-L104)
---
## band-format
Generated enum descriptor for `VipsBandFormat`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:char`
- `:complex`
- `:double`
- `:dpcomplex`
- `:float`
- `:int`
- `:notset`
- `:short`
- `:uchar`
- `:uint`
- `:ushort`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L106-L123)
---
## blend-mode
Generated enum descriptor for `VipsBlendMode`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:add`
- `:atop`
- `:clear`
- `:colour-burn`
- `:colour-dodge`
- `:darken`
- `:dest`
- `:dest-atop`
- `:dest-in`
- `:dest-out`
- `:dest-over`
- `:difference`
- `:exclusion`
- `:hard-light`
- `:in`
- `:lighten`
- `:multiply`
- `:out`
- `:over`
- `:overlay`
- `:saturate`
- `:screen`
- `:soft-light`
- `:source`
- `:xor`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L125-L156)
---
## coding
Generated enum descriptor for `VipsCoding`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:error`
- `:labq`
- `:none`
- `:rad`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L158-L168)
---
## combine
Generated enum descriptor for `VipsCombine`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:max`
- `:min`
- `:sum`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L170-L179)
---
## combine-mode
Generated enum descriptor for `VipsCombineMode`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:add`
- `:set`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L181-L189)
---
## compass-direction
Generated enum descriptor for `VipsCompassDirection`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:centre`
- `:east`
- `:north`
- `:north-east`
- `:north-west`
- `:south`
- `:south-east`
- `:south-west`
- `:west`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L191-L206)
---
## direction
Generated enum descriptor for `VipsDirection`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:horizontal`
- `:vertical`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L208-L216)
---
## extend
Generated enum descriptor for `VipsExtend`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:background`
- `:black`
- `:copy`
- `:mirror`
- `:repeat`
- `:white`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L218-L230)
---
## fail-on
Generated enum descriptor for `VipsFailOn`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:error`
- `:none`
- `:truncated`
- `:warning`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L232-L242)
---
## foreign-dz-container
Generated enum descriptor for `VipsForeignDzContainer`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:fs`
- `:szi`
- `:zip`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L244-L253)
---
## foreign-dz-depth
Generated enum descriptor for `VipsForeignDzDepth`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:one`
- `:onepixel`
- `:onetile`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L255-L264)
---
## foreign-dz-layout
Generated enum descriptor for `VipsForeignDzLayout`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:dz`
- `:google`
- `:iiif`
- `:iiif3`
- `:zoomify`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L266-L277)
---
## foreign-heif-compression
Generated enum descriptor for `VipsForeignHeifCompression`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:av1`
- `:avc`
- `:hevc`
- `:jpeg`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L279-L289)
---
## foreign-heif-encoder
Generated enum descriptor for `VipsForeignHeifEncoder`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:aom`
- `:auto`
- `:rav1e`
- `:svt`
- `:x265`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L291-L302)
---
## foreign-subsample
Generated enum descriptor for `VipsForeignSubsample`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:auto`
- `:off`
- `:on`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L304-L313)
---
## foreign-tiff-compression
Generated enum descriptor for `VipsForeignTiffCompression`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:ccittfax4`
- `:deflate`
- `:jp2k`
- `:jpeg`
- `:lzw`
- `:none`
- `:packbits`
- `:webp`
- `:zstd`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L315-L330)
---
## foreign-tiff-predictor
Generated enum descriptor for `VipsForeignTiffPredictor`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:float`
- `:horizontal`
- `:none`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L332-L341)
---
## foreign-tiff-resunit
Generated enum descriptor for `VipsForeignTiffResunit`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cm`
- `:inch`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L343-L351)
---
## foreign-webp-preset
Generated enum descriptor for `VipsForeignWebpPreset`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:default`
- `:drawing`
- `:icon`
- `:photo`
- `:picture`
- `:text`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L353-L365)
---
## intent
Generated enum descriptor for `VipsIntent`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:absolute`
- `:auto`
- `:perceptual`
- `:relative`
- `:saturation`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L367-L378)
---
## interesting
Generated enum descriptor for `VipsInteresting`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:all`
- `:attention`
- `:centre`
- `:entropy`
- `:high`
- `:low`
- `:none`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L380-L393)
---
## interpretation
Generated enum descriptor for `VipsInterpretation`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:b-w`
- `:cmc`
- `:cmyk`
- `:error`
- `:fourier`
- `:grey16`
- `:histogram`
- `:hsv`
- `:lab`
- `:labq`
- `:labs`
- `:lch`
- `:matrix`
- `:multiband`
- `:oklab`
- `:oklch`
- `:rgb`
- `:rgb16`
- `:scrgb`
- `:srgb`
- `:xyz`
- `:yxy`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L395-L423)
---
## kernel
Generated enum descriptor for `VipsKernel`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cubic`
- `:lanczos2`
- `:lanczos3`
- `:linear`
- `:mitchell`
- `:mks2013`
- `:mks2021`
- `:nearest`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L425-L439)
---
## operation-boolean
Generated enum descriptor for `VipsOperationBoolean`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:and`
- `:eor`
- `:lshift`
- `:or`
- `:rshift`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L441-L452)
---
## operation-complex
Generated enum descriptor for `VipsOperationComplex`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:conj`
- `:polar`
- `:rect`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L454-L463)
---
## operation-complex2
Generated enum descriptor for `VipsOperationComplex2`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:cross-phase`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L465-L472)
---
## operation-complexget
Generated enum descriptor for `VipsOperationComplexget`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:imag`
- `:real`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L474-L482)
---
## operation-math
Generated enum descriptor for `VipsOperationMath`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:acos`
- `:acosh`
- `:asin`
- `:asinh`
- `:atan`
- `:atanh`
- `:cos`
- `:cosh`
- `:exp`
- `:exp10`
- `:log`
- `:log10`
- `:sin`
- `:sinh`
- `:tan`
- `:tanh`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L484-L506)
---
## operation-math2
Generated enum descriptor for `VipsOperationMath2`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:atan2`
- `:pow`
- `:wop`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L508-L517)
---
## operation-morphology
Generated enum descriptor for `VipsOperationMorphology`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:dilate`
- `:erode`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L519-L527)
---
## operation-relational
Generated enum descriptor for `VipsOperationRelational`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:equal`
- `:less`
- `:lesseq`
- `:more`
- `:moreeq`
- `:noteq`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L529-L541)
---
## operation-round
Generated enum descriptor for `VipsOperationRound`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:ceil`
- `:floor`
- `:rint`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L543-L552)
---
## pcs
Generated enum descriptor for `VipsPCS`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:lab`
- `:xyz`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L554-L562)
---
## precision
Generated enum descriptor for `VipsPrecision`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:approximate`
- `:float`
- `:integer`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L564-L573)
---
## region-shrink
Generated enum descriptor for `VipsRegionShrink`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:max`
- `:mean`
- `:median`
- `:min`
- `:mode`
- `:nearest`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L575-L587)
---
## sdf-shape
Generated enum descriptor for `VipsSdfShape`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:box`
- `:circle`
- `:line`
- `:rounded-box`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L589-L599)
---
## size
Generated enum descriptor for `VipsSize`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:both`
- `:down`
- `:force`
- `:up`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L601-L611)
---
## text-wrap
Generated enum descriptor for `VipsTextWrap`.
Use this value directly or look it up in [`registry`](#registry) when you want the generated enum map.
Known values:
- `:char`
- `:none`
- `:word`
- `:word-char`
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L613-L623)
---
## enums
```clojure
(enums)
```
Lists the generated libvips enum ids.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/enums.clj#L625-L625)
## ol.vips.operations
# ol.vips.operations
Generated libvips operation wrappers keyed by normalized operation id.
Inspect [`registry`](#registry) directly for generated operation metadata, then call the generated wrapper vars directly for typed access to supported libvips operations.
## registry
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L12-L252)
---
## abs
```clojure
(abs input)
```
absolute value of an image
Operation name: `abs`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L254-L270)
---
## add
```clojure
(add left right)
```
add two images
Operation name: `add`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L272-L289)
---
## addalpha
```clojure
(addalpha input)
```
append an alpha channel
Operation name: `addalpha`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L291-L307)
---
## affine
```clojure
(affine input matrix)
(affine input matrix opts)
```
affine transform of an image
Operation name: `affine`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `matrix` -> `:matrix` (seqable of number): Transformation matrix
Optional inputs:
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
- `background` -> `:background` (seqable of number): Background value
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L309-L333)
---
## arrayjoin
```clojure
(arrayjoin input)
(arrayjoin input opts)
```
join an array of images
Operation name: `arrayjoin`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- `across` -> `:across` (integer): Number of images across grid
- `shim` -> `:shim` (integer): Pixels between images
- `background` -> `:background` (seqable of number): Colour for new pixels
- `halign` -> `:halign` (keyword, see `ol.vips.enums/align`): Align on the left, centre or right
- `valign` -> `:valign` (keyword, see `ol.vips.enums/align`): Align on the top, centre or bottom
- `hspacing` -> `:hspacing` (integer): Horizontal spacing between images
- `vspacing` -> `:vspacing` (integer): Vertical spacing between images
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L335-L358)
---
## autorot
```clojure
(autorot input)
```
autorotate image by exif tag
Operation name: `autorot`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Angle image was rotated by
- `flip` -> `:flip` (boolean): Whether the image was flipped or not
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L360-L378)
---
## avg
```clojure
(avg input)
```
find image average
Operation name: `avg`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L380-L396)
---
## bandbool
```clojure
(bandbool input boolean)
```
boolean operation across image bands
Operation name: `bandbool`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L398-L415)
---
## bandfold
```clojure
(bandfold input)
(bandfold input opts)
```
fold up x axis into bands
Operation name: `bandfold`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `factor` -> `:factor` (integer): Fold by this factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L417-L434)
---
## bandjoin
```clojure
(bandjoin input)
```
bandwise join a set of images
Operation name: `bandjoin`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L436-L452)
---
## bandjoin-const
```clojure
(bandjoin-const input c)
```
append a constant band to an image
Operation name: `bandjoin_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `c` -> `:c` (seqable of number): Array of constants to add
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L454-L471)
---
## bandmean
```clojure
(bandmean input)
```
band-wise average
Operation name: `bandmean`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L473-L489)
---
## bandrank
```clojure
(bandrank input)
(bandrank input opts)
```
band-wise rank of a set of images
Operation name: `bandrank`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- `index` -> `:index` (integer): Select this band element from sorted list
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L491-L508)
---
## bandunfold
```clojure
(bandunfold input)
(bandunfold input opts)
```
unfold image bands into x axis
Operation name: `bandunfold`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `factor` -> `:factor` (integer): Unfold by this factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L510-L527)
---
## black
```clojure
(black width height)
(black width height opts)
```
make a black image
Operation name: `black`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `bands` -> `:bands` (integer): Number of bands in image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L529-L547)
---
## boolean
```clojure
(boolean left right boolean)
```
boolean operation on two images
Operation name: `boolean`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L549-L567)
---
## boolean-const
```clojure
(boolean-const input boolean c)
```
boolean operations against a constant
Operation name: `boolean_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `boolean` -> `:boolean` (keyword, see `ol.vips.enums/operation-boolean`): Boolean to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L569-L587)
---
## buildlut
```clojure
(buildlut input)
```
build a look-up table
Operation name: `buildlut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Matrix of XY coordinates
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L589-L605)
---
## byteswap
```clojure
(byteswap input)
```
byteswap an image
Operation name: `byteswap`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L607-L623)
---
## cache
```clojure
(cache input)
(cache input opts)
```
cache an image
Operation name: `cache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-tiles` -> `:max-tiles` (integer): Maximum number of tiles to cache
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L625-L644)
---
## canny
```clojure
(canny input)
(canny input opts)
```
Canny edge detector
Operation name: `canny`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L646-L664)
---
## case
```clojure
(case index cases)
```
use pixel values to pick cases from an array of images
Operation name: `case`.
Returns an image handle.
Required inputs:
- `index` -> `:index` (image): Index image
- `cases` -> `:cases` (seqable of image): Array of case images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L666-L683)
---
## cast
```clojure
(cast input format)
(cast input format opts)
```
cast an image
Operation name: `cast`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Format to cast to
Optional inputs:
- `shift` -> `:shift` (boolean): Shift integer values up and down
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L685-L703)
---
## clamp
```clojure
(clamp input)
(clamp input opts)
```
clamp values of an image
Operation name: `clamp`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `min` -> `:min` (float): Minimum value
- `max` -> `:max` (float): Maximum value
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L705-L723)
---
## cmc2-lch
```clojure
(cmc2-lch input)
```
transform LCh to CMC
Operation name: `CMC2LCh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L725-L741)
---
## cmyk2-xyz
```clojure
(cmyk2-xyz input)
```
transform CMYK to XYZ
Operation name: `CMYK2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L743-L759)
---
## colourspace
```clojure
(colourspace input space)
(colourspace input space opts)
```
convert to a new colorspace
Operation name: `colourspace`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `space` -> `:space` (keyword, see `ol.vips.enums/interpretation`): Destination color space
Optional inputs:
- `source-space` -> `:source-space` (keyword, see `ol.vips.enums/interpretation`): Source color space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L761-L779)
---
## compass
```clojure
(compass input mask)
(compass input mask opts)
```
convolve with rotating mask
Operation name: `compass`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `times` -> `:times` (integer): Rotate and convolve this many times
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle45`): Rotate mask by this much between convolutions
- `combine` -> `:combine` (keyword, see `ol.vips.enums/combine`): Combine convolution results like this
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L781-L804)
---
## complex
```clojure
(complex input cmplx)
```
perform a complex operation on an image
Operation name: `complex`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `cmplx` -> `:cmplx` (keyword, see `ol.vips.enums/operation-complex`): Complex to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L806-L823)
---
## complex2
```clojure
(complex2 left right cmplx)
```
complex binary operations on two images
Operation name: `complex2`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `cmplx` -> `:cmplx` (keyword, see `ol.vips.enums/operation-complex2`): Binary complex operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L825-L843)
---
## complexform
```clojure
(complexform left right)
```
form a complex image from two real images
Operation name: `complexform`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L845-L862)
---
## complexget
```clojure
(complexget input get)
```
get a component from a complex image
Operation name: `complexget`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `get` -> `:get` (keyword, see `ol.vips.enums/operation-complexget`): Complex to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L864-L881)
---
## composite2
```clojure
(composite2 base overlay mode)
(composite2 base overlay mode opts)
```
blend a pair of images with a blend mode
Operation name: `composite2`.
Returns an image handle.
Required inputs:
- `base` -> `:base` (image): Base image
- `overlay` -> `:overlay` (image): Overlay image
- `mode` -> `:mode` (keyword, see `ol.vips.enums/blend-mode`): VipsBlendMode to join with
Optional inputs:
- `x` -> `:x` (integer): x position of overlay
- `y` -> `:y` (integer): y position of overlay
- `compositing-space` -> `:compositing-space` (keyword, see `ol.vips.enums/interpretation`): Composite images in this colour space
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L883-L905)
---
## conv
```clojure
(conv input mask)
(conv input mask opts)
```
convolution operation
Operation name: `conv`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L907-L927)
---
## conva
```clojure
(conva input mask)
(conva input mask opts)
```
approximate integer convolution
Operation name: `conva`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L929-L948)
---
## convasep
```clojure
(convasep input mask)
(convasep input mask opts)
```
approximate separable integer convolution
Operation name: `convasep`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `layers` -> `:layers` (integer): Use this many layers in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L950-L968)
---
## convf
```clojure
(convf input mask)
```
float convolution operation
Operation name: `convf`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L970-L987)
---
## convi
```clojure
(convi input mask)
```
int convolution operation
Operation name: `convi`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L989-L1006)
---
## convsep
```clojure
(convsep input mask)
(convsep input mask opts)
```
separable convolution operation
Operation name: `convsep`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
Optional inputs:
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
- `layers` -> `:layers` (integer): Use this many layers in approximation
- `cluster` -> `:cluster` (integer): Cluster lines closer than this in approximation
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1008-L1028)
---
## copy
```clojure
(copy input)
(copy input opts)
```
copy an image
Operation name: `copy`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `swap` -> `:swap` (boolean): Swap bytes in image between little and big-endian
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `bands` -> `:bands` (integer): Number of bands in image
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Pixel format in image
- `coding` -> `:coding` (keyword, see `ol.vips.enums/coding`): Pixel coding
- `interpretation` -> `:interpretation` (keyword, see `ol.vips.enums/interpretation`): Pixel interpretation
- `xres` -> `:xres` (float): Horizontal resolution in pixels/mm
- `yres` -> `:yres` (float): Vertical resolution in pixels/mm
- `xoffset` -> `:xoffset` (integer): Horizontal offset of origin
- `yoffset` -> `:yoffset` (integer): Vertical offset of origin
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1030-L1057)
---
## countlines
```clojure
(countlines input direction)
```
count lines in an image
Operation name: `countlines`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Countlines left-right or up-down
Optional inputs:
- none
Outputs:
- `nolines` -> `:nolines` (float): Number of lines
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1059-L1076)
---
## csvload
```clojure
(csvload filename)
(csvload filename opts)
```
load csv
Operation name: `csvload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `skip` -> `:skip` (integer): Skip this many lines at the start of the file
- `lines` -> `:lines` (integer): Read this many lines from the file
- `whitespace` -> `:whitespace` (string): Set of whitespace characters
- `separator` -> `:separator` (string): Set of separator characters
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1078-L1106)
---
## csvsave
```clojure
(csvsave input filename)
(csvsave input filename opts)
```
save image to csv
Operation name: `csvsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `separator` -> `:separator` (string): Separator characters
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1108-L1131)
---
## d-e00
```clojure
(d-e00 left right)
```
calculate dE00
Operation name: `dE00`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1133-L1150)
---
## d-e76
```clojure
(d-e76 left right)
```
calculate dE76
Operation name: `dE76`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1152-L1169)
---
## d-ecmc
```clojure
(d-ecmc left right)
```
calculate dECMC
Operation name: `dECMC`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand input image
- `right` -> `:right` (image): Right-hand input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1171-L1188)
---
## deviate
```clojure
(deviate input)
```
find image standard deviation
Operation name: `deviate`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1190-L1206)
---
## divide
```clojure
(divide left right)
```
divide two images
Operation name: `divide`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1208-L1225)
---
## draw-circle
```clojure
(draw-circle image ink cx cy radius)
(draw-circle image ink cx cy radius opts)
```
draw a circle on an image
Operation name: `draw_circle`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `cx` -> `:cx` (integer): Centre of draw_circle
- `cy` -> `:cy` (integer): Centre of draw_circle
- `radius` -> `:radius` (integer): Radius in pixels
Optional inputs:
- `fill` -> `:fill` (boolean): Draw a solid object
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1227-L1248)
---
## draw-flood
```clojure
(draw-flood image ink x y)
(draw-flood image ink x y opts)
```
flood-fill an area
Operation name: `draw_flood`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `x` -> `:x` (integer): DrawFlood start point
- `y` -> `:y` (integer): DrawFlood start point
Optional inputs:
- `test` -> `:test` (image): Test pixels in this image
- `equal` -> `:equal` (boolean): DrawFlood while equal to edge
Outputs:
- `left` -> `:left` (integer): Left edge of modified area
- `top` -> `:top` (integer): Top edge of modified area
- `width` -> `:width` (integer): Width of modified area
- `height` -> `:height` (integer): Height of modified area
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1250-L1274)
---
## draw-image
```clojure
(draw-image image sub x y)
(draw-image image sub x y opts)
```
paint an image into another image
Operation name: `draw_image`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `sub` -> `:sub` (image): Sub-image to insert into main image
- `x` -> `:x` (integer): Draw image here
- `y` -> `:y` (integer): Draw image here
Optional inputs:
- `mode` -> `:mode` (keyword, see `ol.vips.enums/combine-mode`): Combining mode
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1276-L1296)
---
## draw-line
```clojure
(draw-line image ink x1 y1 x2 y2)
```
draw a line on an image
Operation name: `draw_line`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `x1` -> `:x1` (integer): Start of draw_line
- `y1` -> `:y1` (integer): Start of draw_line
- `x2` -> `:x2` (integer): End of draw_line
- `y2` -> `:y2` (integer): End of draw_line
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1298-L1319)
---
## draw-mask
```clojure
(draw-mask image ink mask x y)
```
draw a mask on an image
Operation name: `draw_mask`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `mask` -> `:mask` (image): Mask of pixels to draw
- `x` -> `:x` (integer): Draw mask here
- `y` -> `:y` (integer): Draw mask here
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1321-L1341)
---
## draw-rect
```clojure
(draw-rect image ink left top width height)
(draw-rect image ink left top width height opts)
```
paint a rectangle on an image
Operation name: `draw_rect`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `ink` -> `:ink` (seqable of number): Color for pixels
- `left` -> `:left` (integer): Rect to fill
- `top` -> `:top` (integer): Rect to fill
- `width` -> `:width` (integer): Rect to fill
- `height` -> `:height` (integer): Rect to fill
Optional inputs:
- `fill` -> `:fill` (boolean): Draw a solid object
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1343-L1365)
---
## draw-smudge
```clojure
(draw-smudge image left top width height)
```
blur a rectangle on an image
Operation name: `draw_smudge`.
Returns a result map.
Required inputs:
- `image` -> `:image` (image): Image to draw on
- `left` -> `:left` (integer): Rect to fill
- `top` -> `:top` (integer): Rect to fill
- `width` -> `:width` (integer): Rect to fill
- `height` -> `:height` (integer): Rect to fill
Optional inputs:
- none
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1367-L1387)
---
## dzsave
```clojure
(dzsave input filename)
(dzsave input filename opts)
```
save image to deepzoom file
Operation name: `dzsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `dirname` -> `:dirname` (string): Directory name to save to
- `imagename` -> `:imagename` (string): Image name
- `layout` -> `:layout` (keyword, see `ol.vips.enums/foreign-dz-layout`): Directory layout
- `suffix` -> `:suffix` (string): Filename suffix for tiles
- `overlap` -> `:overlap` (integer): Tile overlap in pixels
- `tile-size` -> `:tile-size` (integer): Tile size in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `centre` -> `:centre` (boolean): Center image in tile
- `depth` -> `:depth` (keyword, see `ol.vips.enums/foreign-dz-depth`): Pyramid depth
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Rotate image during save
- `container` -> `:container` (keyword, see `ol.vips.enums/foreign-dz-container`): Pyramid container type
- `properties` -> `:properties` (boolean): Write a properties file to the output directory
- `compression` -> `:compression` (integer): ZIP deflate compression level
- `region-shrink` -> `:region-shrink` (keyword, see `ol.vips.enums/region-shrink`): Method to shrink regions
- `skip-blanks` -> `:skip-blanks` (integer): Skip tiles which are nearly equal to the background
- `id` -> `:id` (string): Resource ID
- `q` -> `:Q` (integer): Q factor
- `no-strip` -> `:no-strip` (boolean): Don’t strip tile metadata
- `basename` -> `:basename` (string): Base name to save to
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1389-L1431)
---
## embed
```clojure
(embed input x y width height)
(embed input x y width height opts)
```
embed an image in a larger image
Operation name: `embed`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `x` -> `:x` (integer): Left edge of input in output
- `y` -> `:y` (integer): Top edge of input in output
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
- `background` -> `:background` (seqable of number): Color for background pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1433-L1455)
---
## extract-area
```clojure
(extract-area input left top width height)
```
extract an area from an image
Operation name: `extract_area`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `left` -> `:left` (integer): Left edge of extract area
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1457-L1477)
---
## extract-band
```clojure
(extract-band input band)
(extract-band input band opts)
```
extract band from an image
Operation name: `extract_band`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `band` -> `:band` (integer): Band to extract
Optional inputs:
- `n` -> `:n` (integer): Number of bands to extract
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1479-L1497)
---
## eye
```clojure
(eye width height)
(eye width height opts)
```
make an image showing the eye’s spatial response
Operation name: `eye`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `factor` -> `:factor` (float): Maximum spatial frequency
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1499-L1518)
---
## falsecolour
```clojure
(falsecolour input)
```
false-color an image
Operation name: `falsecolour`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1520-L1536)
---
## fastcor
```clojure
(fastcor input ref)
```
fast correlation
Operation name: `fastcor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `ref` -> `:ref` (image): Input reference image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1538-L1555)
---
## fill-nearest
```clojure
(fill-nearest input)
```
fill image zeros with nearest non-zero pixel
Operation name: `fill_nearest`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Value of nearest non-zero pixel
- `distance` -> `:distance` (image): Distance to nearest non-zero pixel
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1557-L1574)
---
## find-trim
```clojure
(find-trim input)
(find-trim input opts)
```
search an image for non-edge areas
Operation name: `find_trim`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to find_trim
Optional inputs:
- `threshold` -> `:threshold` (float): Object threshold
- `background` -> `:background` (seqable of number): Color for background pixels
- `line-art` -> `:line-art` (boolean): Enable line art mode
Outputs:
- `left` -> `:left` (integer): Left edge of image
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1576-L1598)
---
## flatten
```clojure
(flatten input)
(flatten input opts)
```
flatten alpha out of an image
Operation name: `flatten`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1600-L1618)
---
## flip
```clojure
(flip input direction)
```
flip an image
Operation name: `flip`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Direction to flip image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1620-L1637)
---
## float2rad
```clojure
(float2rad input)
```
transform float RGB to Radiance coding
Operation name: `float2rad`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1639-L1655)
---
## fractsurf
```clojure
(fractsurf width height fractal-dimension)
```
make a fractal surface
Operation name: `fractsurf`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `fractal-dimension` -> `:fractal-dimension` (float): Fractal dimension
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1657-L1675)
---
## freqmult
```clojure
(freqmult input mask)
```
frequency-domain filtering
Operation name: `freqmult`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `mask` -> `:mask` (image): Input mask image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1677-L1694)
---
## gamma
```clojure
(gamma input)
(gamma input opts)
```
gamma an image
Operation name: `gamma`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `exponent` -> `:exponent` (float): Gamma factor
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1696-L1713)
---
## gaussblur
```clojure
(gaussblur input sigma)
(gaussblur input sigma opts)
```
gaussian blur
Operation name: `gaussblur`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `sigma` -> `:sigma` (float): Sigma of Gaussian
Optional inputs:
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Convolve with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1715-L1734)
---
## gaussmat
```clojure
(gaussmat sigma min-ampl)
(gaussmat sigma min-ampl opts)
```
make a gaussian image
Operation name: `gaussmat`.
Returns an image handle.
Required inputs:
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
Optional inputs:
- `separable` -> `:separable` (boolean): Generate separable Gaussian
- `integer` -> `:integer` (boolean): Generate integer Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Generate with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1736-L1756)
---
## gaussnoise
```clojure
(gaussnoise width height)
(gaussnoise width height opts)
```
make a gaussnoise image
Operation name: `gaussnoise`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `sigma` -> `:sigma` (float): Standard deviation of pixels in generated image
- `mean` -> `:mean` (float): Mean of pixels in generated image
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1758-L1778)
---
## gifload
```clojure
(gifload filename)
(gifload filename opts)
```
load GIF with libnsgif
Operation name: `gifload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `page` -> `:page` (integer): First page to load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1780-L1806)
---
## gifsave
```clojure
(gifsave input filename)
(gifsave input filename opts)
```
save as gif
Operation name: `gifsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `dither` -> `:dither` (float): Amount of dithering
- `effort` -> `:effort` (integer): Quantisation effort
- `bitdepth` -> `:bitdepth` (integer): Number of bits per pixel
- `interframe-maxerror` -> `:interframe-maxerror` (float): Maximum inter-frame error for transparency
- `reuse` -> `:reuse` (boolean): Reuse palette from input
- `interpalette-maxerror` -> `:interpalette-maxerror` (float): Maximum inter-palette error for palette reusage
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) GIF
- `reoptimise` -> `:reoptimise` (boolean): Reoptimise colour palettes
- `keep-duplicate-frames` -> `:keep-duplicate-frames` (boolean): Keep duplicate frames in the output instead of combining them
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1808-L1839)
---
## globalbalance
```clojure
(globalbalance input)
(globalbalance input opts)
```
global balance an image mosaic
Operation name: `globalbalance`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `gamma` -> `:gamma` (float): Image gamma
- `int-output` -> `:int-output` (boolean): Integer output
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1841-L1859)
---
## gravity
```clojure
(gravity input direction width height)
(gravity input direction width height opts)
```
place an image within a larger image with a certain gravity
Operation name: `gravity`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/compass-direction`): Direction to place image within width/height
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
- `background` -> `:background` (seqable of number): Color for background pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1861-L1882)
---
## grey
```clojure
(grey width height)
(grey width height opts)
```
make a grey ramp image
Operation name: `grey`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1884-L1902)
---
## grid
```clojure
(grid input tile-height across down)
```
grid an image
Operation name: `grid`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `tile-height` -> `:tile-height` (integer): Chop into tiles this high
- `across` -> `:across` (integer): Number of tiles across
- `down` -> `:down` (integer): Number of tiles down
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1904-L1923)
---
## heifload
```clojure
(heifload filename)
(heifload filename opts)
```
load a HEIF image
Operation name: `heifload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `thumbnail` -> `:thumbnail` (boolean): Fetch thumbnail image
- `autorotate` -> `:autorotate` (boolean): Rotate image using exif orientation
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1925-L1954)
---
## heifsave
```clojure
(heifsave input filename)
(heifsave input filename opts)
```
save image in HEIF format
Operation name: `heifsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `bitdepth` -> `:bitdepth` (integer): Number of bits per pixel
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `compression` -> `:compression` (keyword, see `ol.vips.enums/foreign-heif-compression`): Compression format
- `effort` -> `:effort` (integer): CPU effort
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `speed` -> `:speed` (integer): CPU effort
- `encoder` -> `:encoder` (keyword, see `ol.vips.enums/foreign-heif-encoder`): Select encoder to use
- `tune` -> `:tune` (string): Tuning parameters
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1956-L1987)
---
## hist-cum
```clojure
(hist-cum input)
```
form cumulative histogram
Operation name: `hist_cum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L1989-L2005)
---
## hist-entropy
```clojure
(hist-entropy input)
```
estimate image entropy
Operation name: `hist_entropy`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:in` (image): Input histogram image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (float): Output value
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2007-L2023)
---
## hist-equal
```clojure
(hist-equal input)
(hist-equal input opts)
```
histogram equalisation
Operation name: `hist_equal`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Equalise with this band
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2025-L2042)
---
## hist-find
```clojure
(hist-find input)
(hist-find input opts)
```
find image histogram
Operation name: `hist_find`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Find histogram of band
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2044-L2061)
---
## hist-find-indexed
```clojure
(hist-find-indexed input index)
(hist-find-indexed input index opts)
```
find indexed image histogram
Operation name: `hist_find_indexed`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `index` -> `:index` (image): Index image
Optional inputs:
- `combine` -> `:combine` (keyword, see `ol.vips.enums/combine`): Combine bins like this
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2063-L2081)
---
## hist-find-ndim
```clojure
(hist-find-ndim input)
(hist-find-ndim input opts)
```
find n-dimensional image histogram
Operation name: `hist_find_ndim`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `bins` -> `:bins` (integer): Number of bins in each dimension
Outputs:
- `out` -> `:out` (image): Output histogram
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2083-L2100)
---
## hist-ismonotonic
```clojure
(hist-ismonotonic input)
```
test for monotonicity
Operation name: `hist_ismonotonic`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input histogram image
Optional inputs:
- none
Outputs:
- `monotonic` -> `:monotonic` (boolean): true if in is monotonic
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2102-L2118)
---
## hist-local
```clojure
(hist-local input width height)
(hist-local input width height opts)
```
local histogram equalisation
Operation name: `hist_local`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
Optional inputs:
- `max-slope` -> `:max-slope` (integer): Maximum slope (CLAHE)
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2120-L2139)
---
## hist-match
```clojure
(hist-match input ref)
```
match two histograms
Operation name: `hist_match`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input histogram
- `ref` -> `:ref` (image): Reference histogram
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2141-L2158)
---
## hist-norm
```clojure
(hist-norm input)
```
normalise histogram
Operation name: `hist_norm`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2160-L2176)
---
## hist-plot
```clojure
(hist-plot input)
```
plot histogram
Operation name: `hist_plot`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2178-L2194)
---
## hough-circle
```clojure
(hough-circle input)
(hough-circle input opts)
```
find hough circle transform
Operation name: `hough_circle`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `scale` -> `:scale` (integer): Scale down dimensions by this factor
- `min-radius` -> `:min-radius` (integer): Smallest radius to search for
- `max-radius` -> `:max-radius` (integer): Largest radius to search for
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2196-L2215)
---
## hough-line
```clojure
(hough-line input)
(hough-line input opts)
```
find hough line transform
Operation name: `hough_line`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `width` -> `:width` (integer): Horizontal size of parameter space
- `height` -> `:height` (integer): Vertical size of parameter space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2217-L2235)
---
## hsv2s-rgb
```clojure
(hsv2s-rgb input)
```
transform HSV to sRGB
Operation name: `HSV2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2237-L2253)
---
## icc-export
```clojure
(icc-export input)
(icc-export input opts)
```
output to device with ICC profile
Operation name: `icc_export`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `output-profile` -> `:output-profile` (string): Filename to load output profile from
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2255-L2276)
---
## icc-import
```clojure
(icc-import input)
(icc-import input opts)
```
import from device with ICC profile
Operation name: `icc_import`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `embedded` -> `:embedded` (boolean): Use embedded input profile, if available
- `input-profile` -> `:input-profile` (string): Filename to load input profile from
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2278-L2299)
---
## icc-transform
```clojure
(icc-transform input output-profile)
(icc-transform input output-profile opts)
```
transform between devices with ICC profiles
Operation name: `icc_transform`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `output-profile` -> `:output-profile` (string): Filename to load output profile from
Optional inputs:
- `pcs` -> `:pcs` (keyword, see `ol.vips.enums/pcs`): Set Profile Connection Space
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `black-point-compensation` -> `:black-point-compensation` (boolean): Enable black point compensation
- `embedded` -> `:embedded` (boolean): Use embedded input profile, if available
- `input-profile` -> `:input-profile` (string): Filename to load input profile from
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2301-L2324)
---
## identity
```clojure
(identity)
(identity opts)
```
make a 1D image where pixel values are indexes
Operation name: `identity`.
Returns an image handle.
Required inputs:
- none
Optional inputs:
- `bands` -> `:bands` (integer): Number of bands in LUT
- `ushort` -> `:ushort` (boolean): Create a 16-bit LUT
- `size` -> `:size` (integer): Size of 16-bit LUT
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2326-L2345)
---
## ifthenelse
```clojure
(ifthenelse cond in1 in2)
(ifthenelse cond in1 in2 opts)
```
ifthenelse an image
Operation name: `ifthenelse`.
Returns an image handle.
Required inputs:
- `cond` -> `:cond` (image): Condition input image
- `in1` -> `:in1` (image): Source for TRUE pixels
- `in2` -> `:in2` (image): Source for FALSE pixels
Optional inputs:
- `blend` -> `:blend` (boolean): Blend smoothly between then and else parts
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2347-L2366)
---
## insert
```clojure
(insert main sub x y)
(insert main sub x y opts)
```
insert image @sub into @main at @x, @y
Operation name: `insert`.
Returns an image handle.
Required inputs:
- `main` -> `:main` (image): Main input image
- `sub` -> `:sub` (image): Sub-image to insert into main image
- `x` -> `:x` (integer): Left edge of sub in main
- `y` -> `:y` (integer): Top edge of sub in main
Optional inputs:
- `expand` -> `:expand` (boolean): Expand output to hold all of both inputs
- `background` -> `:background` (seqable of number): Color for new pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2368-L2389)
---
## invert
```clojure
(invert input)
```
invert an image
Operation name: `invert`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2391-L2407)
---
## invertlut
```clojure
(invertlut input)
(invertlut input opts)
```
build an inverted look-up table
Operation name: `invertlut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Matrix of XY coordinates
Optional inputs:
- `size` -> `:size` (integer): LUT size to generate
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2409-L2426)
---
## join
```clojure
(join in1 in2 direction)
(join in1 in2 direction opts)
```
join a pair of images
Operation name: `join`.
Returns an image handle.
Required inputs:
- `in1` -> `:in1` (image): First input image
- `in2` -> `:in2` (image): Second input image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Join left-right or up-down
Optional inputs:
- `expand` -> `:expand` (boolean): Expand output to hold all of both inputs
- `shim` -> `:shim` (integer): Pixels between images
- `background` -> `:background` (seqable of number): Colour for new pixels
- `align` -> `:align` (keyword, see `ol.vips.enums/align`): Align on the low, centre or high coordinate edge
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2428-L2450)
---
## jpegload
```clojure
(jpegload filename)
(jpegload filename opts)
```
load jpeg from file
Operation name: `jpegload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `autorotate` -> `:autorotate` (boolean): Rotate image using exif orientation
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2452-L2479)
---
## jpegsave
```clojure
(jpegsave input filename)
(jpegsave input filename opts)
```
save as jpeg
Operation name: `jpegsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `optimize-coding` -> `:optimize-coding` (boolean): Compute optimal Huffman coding tables
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) jpeg
- `no-subsample` -> `:no-subsample` (boolean): Disable chroma subsample
- `trellis-quant` -> `:trellis-quant` (boolean): Apply trellis quantisation to each 8x8 block
- `overshoot-deringing` -> `:overshoot-deringing` (boolean): Apply overshooting to samples with extreme values
- `optimize-scans` -> `:optimize-scans` (boolean): Split spectrum of DCT coefficients into separate scans
- `quant-table` -> `:quant-table` (integer): Use predefined quantization table with given index
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `restart-interval` -> `:restart-interval` (integer): Add restart markers every specified number of mcu
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2481-L2513)
---
## jpegsave-mime
```clojure
(jpegsave-mime input)
(jpegsave-mime input opts)
```
save image to jpeg mime
Operation name: `jpegsave_mime`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `optimize-coding` -> `:optimize-coding` (boolean): Compute optimal Huffman coding tables
- `interlace` -> `:interlace` (boolean): Generate an interlaced (progressive) jpeg
- `no-subsample` -> `:no-subsample` (boolean): Disable chroma subsample
- `trellis-quant` -> `:trellis-quant` (boolean): Apply trellis quantisation to each 8x8 block
- `overshoot-deringing` -> `:overshoot-deringing` (boolean): Apply overshooting to samples with extreme values
- `optimize-scans` -> `:optimize-scans` (boolean): Split spectrum of DCT coefficients into separate scans
- `quant-table` -> `:quant-table` (integer): Use predefined quantization table with given index
- `subsample-mode` -> `:subsample-mode` (keyword, see `ol.vips.enums/foreign-subsample`): Select chroma subsample operation mode
- `restart-interval` -> `:restart-interval` (integer): Add restart markers every specified number of mcu
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2515-L2546)
---
## lab-q2-lab
```clojure
(lab-q2-lab input)
```
unpack a LabQ image to float Lab
Operation name: `LabQ2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2548-L2564)
---
## lab-q2-lab-s
```clojure
(lab-q2-lab-s input)
```
unpack a LabQ image to short Lab
Operation name: `LabQ2LabS`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2566-L2582)
---
## lab-q2s-rgb
```clojure
(lab-q2s-rgb input)
```
convert a LabQ image to sRGB
Operation name: `LabQ2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2584-L2600)
---
## lab-s2-lab
```clojure
(lab-s2-lab input)
```
transform signed short Lab to float
Operation name: `LabS2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2602-L2618)
---
## lab-s2-lab-q
```clojure
(lab-s2-lab-q input)
```
transform short Lab to LabQ coding
Operation name: `LabS2LabQ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2620-L2636)
---
## lab2-lab-q
```clojure
(lab2-lab-q input)
```
transform float Lab to LabQ coding
Operation name: `Lab2LabQ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2638-L2654)
---
## lab2-lab-s
```clojure
(lab2-lab-s input)
```
transform float Lab to signed short
Operation name: `Lab2LabS`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2656-L2672)
---
## lab2-lch
```clojure
(lab2-lch input)
```
transform Lab to LCh
Operation name: `Lab2LCh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2674-L2690)
---
## lab2-xyz
```clojure
(lab2-xyz input)
(lab2-xyz input opts)
```
transform CIELAB to XYZ
Operation name: `Lab2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `temp` -> `:temp` (seqable of number): Color temperature
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2692-L2709)
---
## labelregions
```clojure
(labelregions input)
```
label regions in an image
Operation name: `labelregions`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- none
Outputs:
- `mask` -> `:mask` (image): Mask of region labels
- `segments` -> `:segments` (integer): Number of discrete contiguous regions
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2711-L2728)
---
## lch2-cmc
```clojure
(lch2-cmc input)
```
transform LCh to CMC
Operation name: `LCh2CMC`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2730-L2746)
---
## lch2-lab
```clojure
(lch2-lab input)
```
transform LCh to Lab
Operation name: `LCh2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2748-L2764)
---
## linear
```clojure
(linear input a b)
(linear input a b opts)
```
calculate (a * in + b)
Operation name: `linear`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `a` -> `:a` (seqable of number): Multiply by this
- `b` -> `:b` (seqable of number): Add this
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output should be uchar
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2766-L2785)
---
## linecache
```clojure
(linecache input)
(linecache input opts)
```
cache an image as a set of lines
Operation name: `linecache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
- `threaded` -> `:threaded` (boolean): Allow threaded access
- `persistent` -> `:persistent` (boolean): Keep cache between evaluations
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2787-L2807)
---
## logmat
```clojure
(logmat sigma min-ampl)
(logmat sigma min-ampl opts)
```
make a Laplacian of Gaussian image
Operation name: `logmat`.
Returns an image handle.
Required inputs:
- `sigma` -> `:sigma` (float): Radius of Gaussian
- `min-ampl` -> `:min-ampl` (float): Minimum amplitude of Gaussian
Optional inputs:
- `separable` -> `:separable` (boolean): Generate separable Gaussian
- `integer` -> `:integer` (boolean): Generate integer Gaussian
- `precision` -> `:precision` (keyword, see `ol.vips.enums/precision`): Generate with this precision
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2809-L2829)
---
## mapim
```clojure
(mapim input index)
(mapim input index opts)
```
resample with a map image
Operation name: `mapim`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `index` -> `:index` (image): Index pixels with this
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `premultiplied` -> `:premultiplied` (boolean): Images have premultiplied alpha
- `extend` -> `:extend` (keyword, see `ol.vips.enums/extend`): How to generate the extra pixels
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2831-L2851)
---
## maplut
```clojure
(maplut input lut)
(maplut input lut opts)
```
map an image though a lut
Operation name: `maplut`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `lut` -> `:lut` (image): Look-up table image
Optional inputs:
- `band` -> `:band` (integer): Apply one-band lut to this band of in
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2853-L2871)
---
## mask-butterworth
```clojure
(mask-butterworth width height order frequency-cutoff amplitude-cutoff)
(mask-butterworth width height order frequency-cutoff amplitude-cutoff opts)
```
make a butterworth filter
Operation name: `mask_butterworth`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2873-L2897)
---
## mask-butterworth-band
```clojure
(mask-butterworth-band width height order frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff)
(mask-butterworth-band width height order frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff opts)
```
make a butterworth_band filter
Operation name: `mask_butterworth_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2899-L2925)
---
## mask-butterworth-ring
```clojure
(mask-butterworth-ring width height order frequency-cutoff amplitude-cutoff ringwidth)
(mask-butterworth-ring width height order frequency-cutoff amplitude-cutoff ringwidth opts)
```
make a butterworth ring filter
Operation name: `mask_butterworth_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `order` -> `:order` (float): Filter order
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2927-L2952)
---
## mask-fractal
```clojure
(mask-fractal width height fractal-dimension)
(mask-fractal width height fractal-dimension opts)
```
make fractal filter
Operation name: `mask_fractal`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `fractal-dimension` -> `:fractal-dimension` (float): Fractal dimension
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2954-L2976)
---
## mask-gaussian
```clojure
(mask-gaussian width height frequency-cutoff amplitude-cutoff)
(mask-gaussian width height frequency-cutoff amplitude-cutoff opts)
```
make a gaussian filter
Operation name: `mask_gaussian`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L2978-L3001)
---
## mask-gaussian-band
```clojure
(mask-gaussian-band width height frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff)
(mask-gaussian-band width height frequency-cutoff-x frequency-cutoff-y radius amplitude-cutoff opts)
```
make a gaussian filter
Operation name: `mask_gaussian_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3003-L3028)
---
## mask-gaussian-ring
```clojure
(mask-gaussian-ring width height frequency-cutoff amplitude-cutoff ringwidth)
(mask-gaussian-ring width height frequency-cutoff amplitude-cutoff ringwidth opts)
```
make a gaussian ring filter
Operation name: `mask_gaussian_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `amplitude-cutoff` -> `:amplitude-cutoff` (float): Amplitude cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3030-L3054)
---
## mask-ideal
```clojure
(mask-ideal width height frequency-cutoff)
(mask-ideal width height frequency-cutoff opts)
```
make an ideal filter
Operation name: `mask_ideal`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3056-L3078)
---
## mask-ideal-band
```clojure
(mask-ideal-band width height frequency-cutoff-x frequency-cutoff-y radius)
(mask-ideal-band width height frequency-cutoff-x frequency-cutoff-y radius opts)
```
make an ideal band filter
Operation name: `mask_ideal_band`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff-x` -> `:frequency-cutoff-x` (float): Frequency cutoff x
- `frequency-cutoff-y` -> `:frequency-cutoff-y` (float): Frequency cutoff y
- `radius` -> `:radius` (float): Radius of circle
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3080-L3104)
---
## mask-ideal-ring
```clojure
(mask-ideal-ring width height frequency-cutoff ringwidth)
(mask-ideal-ring width height frequency-cutoff ringwidth opts)
```
make an ideal ring filter
Operation name: `mask_ideal_ring`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `frequency-cutoff` -> `:frequency-cutoff` (float): Frequency cutoff
- `ringwidth` -> `:ringwidth` (float): Ringwidth
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `nodc` -> `:nodc` (boolean): Remove DC component
- `reject` -> `:reject` (boolean): Invert the sense of the filter
- `optical` -> `:optical` (boolean): Rotate quadrants to optical space
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3106-L3129)
---
## match
```clojure
(match ref sec xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2)
(match ref sec xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2 opts)
```
first-order match of two images
Operation name: `match`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `xr1` -> `:xr1` (integer): Position of first reference tie-point
- `yr1` -> `:yr1` (integer): Position of first reference tie-point
- `xs1` -> `:xs1` (integer): Position of first secondary tie-point
- `ys1` -> `:ys1` (integer): Position of first secondary tie-point
- `xr2` -> `:xr2` (integer): Position of second reference tie-point
- `yr2` -> `:yr2` (integer): Position of second reference tie-point
- `xs2` -> `:xs2` (integer): Position of second secondary tie-point
- `ys2` -> `:ys2` (integer): Position of second secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `search` -> `:search` (boolean): Search to improve tie-points
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3131-L3159)
---
## math
```clojure
(math input math)
```
apply a math operation to an image
Operation name: `math`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `math` -> `:math` (keyword, see `ol.vips.enums/operation-math`): Math to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3161-L3178)
---
## math2
```clojure
(math2 left right math2)
```
binary math operations
Operation name: `math2`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `math2` -> `:math2` (keyword, see `ol.vips.enums/operation-math2`): Math to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3180-L3198)
---
## math2-const
```clojure
(math2-const input math2 c)
```
binary math operations with a constant
Operation name: `math2_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `math2` -> `:math2` (keyword, see `ol.vips.enums/operation-math2`): Math to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3200-L3218)
---
## matrixinvert
```clojure
(matrixinvert input)
```
invert a matrix
Operation name: `matrixinvert`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): An square matrix
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output matrix
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3220-L3236)
---
## matrixload
```clojure
(matrixload filename)
(matrixload filename opts)
```
load matrix
Operation name: `matrixload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3238-L3262)
---
## matrixmultiply
```clojure
(matrixmultiply left right)
```
multiply two matrices
Operation name: `matrixmultiply`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): First matrix to multiply
- `right` -> `:right` (image): Second matrix to multiply
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output matrix
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3264-L3281)
---
## matrixprint
```clojure
(matrixprint input)
(matrixprint input opts)
```
print matrix
Operation name: `matrixprint`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3283-L3304)
---
## matrixsave
```clojure
(matrixsave input filename)
(matrixsave input filename opts)
```
save image to matrix
Operation name: `matrixsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3306-L3328)
---
## maxpair
```clojure
(maxpair left right)
```
maximum of a pair of images
Operation name: `maxpair`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3330-L3347)
---
## measure
```clojure
(measure input h v)
(measure input h v opts)
```
measure a set of patches on a color chart
Operation name: `measure`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Image to measure
- `h` -> `:h` (integer): Number of patches across chart
- `v` -> `:v` (integer): Number of patches down chart
Optional inputs:
- `left` -> `:left` (integer): Left edge of extract area
- `top` -> `:top` (integer): Top edge of extract area
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Outputs:
- `out` -> `:out` (image): Output array of statistics
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3349-L3371)
---
## merge
```clojure
(merge ref sec direction dx dy)
(merge ref sec direction dx dy opts)
```
merge two images
Operation name: `merge`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical merge
- `dx` -> `:dx` (integer): Horizontal displacement from sec to ref
- `dy` -> `:dy` (integer): Vertical displacement from sec to ref
Optional inputs:
- `mblend` -> `:mblend` (integer): Maximum blend size
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3373-L3394)
---
## minpair
```clojure
(minpair left right)
```
minimum of a pair of images
Operation name: `minpair`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3396-L3413)
---
## morph
```clojure
(morph input mask morph)
```
morphology operation
Operation name: `morph`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `mask` -> `:mask` (image): Input matrix image
- `morph` -> `:morph` (keyword, see `ol.vips.enums/operation-morphology`): Morphological operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3415-L3433)
---
## mosaic
```clojure
(mosaic ref sec direction xref yref xsec ysec)
(mosaic ref sec direction xref yref xsec ysec opts)
```
mosaic two images
Operation name: `mosaic`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical mosaic
- `xref` -> `:xref` (integer): Position of reference tie-point
- `yref` -> `:yref` (integer): Position of reference tie-point
- `xsec` -> `:xsec` (integer): Position of secondary tie-point
- `ysec` -> `:ysec` (integer): Position of secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `mblend` -> `:mblend` (integer): Maximum blend size
- `bandno` -> `:bandno` (integer): Band to search for features on
Outputs:
- `out` -> `:out` (image): Output image
- `dx0` -> `:dx0` (integer): Detected integer offset
- `dy0` -> `:dy0` (integer): Detected integer offset
- `scale1` -> `:scale1` (float): Detected scale
- `angle1` -> `:angle1` (float): Detected rotation
- `dy1` -> `:dy1` (float): Detected first-order displacement
- `dx1` -> `:dx1` (float): Detected first-order displacement
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3435-L3467)
---
## mosaic1
```clojure
(mosaic1 ref sec direction xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2)
(mosaic1 ref sec direction xr1 yr1 xs1 ys1 xr2 yr2 xs2 ys2 opts)
```
first-order mosaic of two images
Operation name: `mosaic1`.
Returns an image handle.
Required inputs:
- `ref` -> `:ref` (image): Reference image
- `sec` -> `:sec` (image): Secondary image
- `direction` -> `:direction` (keyword, see `ol.vips.enums/direction`): Horizontal or vertical mosaic
- `xr1` -> `:xr1` (integer): Position of first reference tie-point
- `yr1` -> `:yr1` (integer): Position of first reference tie-point
- `xs1` -> `:xs1` (integer): Position of first secondary tie-point
- `ys1` -> `:ys1` (integer): Position of first secondary tie-point
- `xr2` -> `:xr2` (integer): Position of second reference tie-point
- `yr2` -> `:yr2` (integer): Position of second reference tie-point
- `xs2` -> `:xs2` (integer): Position of second secondary tie-point
- `ys2` -> `:ys2` (integer): Position of second secondary tie-point
Optional inputs:
- `hwindow` -> `:hwindow` (integer): Half window size
- `harea` -> `:harea` (integer): Half area size
- `search` -> `:search` (boolean): Search to improve tie-points
- `mblend` -> `:mblend` (integer): Maximum blend size
- `bandno` -> `:bandno` (integer): Band to search for features on
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3469-L3500)
---
## msb
```clojure
(msb input)
(msb input opts)
```
pick most-significant byte from an image
Operation name: `msb`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `band` -> `:band` (integer): Band to msb
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3502-L3519)
---
## multiply
```clojure
(multiply left right)
```
multiply two images
Operation name: `multiply`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3521-L3538)
---
## oklab2-oklch
```clojure
(oklab2-oklch input)
```
transform Oklab to Oklch
Operation name: `Oklab2Oklch`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3540-L3556)
---
## oklab2-xyz
```clojure
(oklab2-xyz input)
```
transform Oklab to XYZ
Operation name: `Oklab2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3558-L3574)
---
## oklch2-oklab
```clojure
(oklch2-oklab input)
```
transform Oklch to Oklab
Operation name: `Oklch2Oklab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3576-L3592)
---
## percent
```clojure
(percent input percent)
```
find threshold for percent of pixels
Operation name: `percent`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
- `percent` -> `:percent` (float): Percent of pixels
Optional inputs:
- none
Outputs:
- `threshold` -> `:threshold` (integer): Threshold above which lie percent of pixels
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3594-L3611)
---
## perlin
```clojure
(perlin width height)
(perlin width height opts)
```
make a perlin noise image
Operation name: `perlin`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `cell-size` -> `:cell-size` (integer): Size of Perlin cells
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3613-L3633)
---
## phasecor
```clojure
(phasecor input in2)
```
calculate phase correlation
Operation name: `phasecor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `in2` -> `:in2` (image): Second input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3635-L3652)
---
## pngload
```clojure
(pngload filename)
(pngload filename opts)
```
load png from file
Operation name: `pngload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3654-L3679)
---
## pngsave
```clojure
(pngsave input filename)
(pngsave input filename opts)
```
save image to file as png
Operation name: `pngsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `compression` -> `:compression` (integer): Compression factor
- `interlace` -> `:interlace` (boolean): Interlace image
- `filter` -> `:filter` (integer flags): libpng row filter flag(s)
- `palette` -> `:palette` (boolean): Quantise to 8bpp palette
- `colours` -> `:colours` (integer): Max number of palette colours
- `q` -> `:Q` (integer): Quantisation quality
- `dither` -> `:dither` (float): Amount of dithering
- `bitdepth` -> `:bitdepth` (integer): Write as a 1, 2, 4, 8 or 16 bit image
- `effort` -> `:effort` (integer): Quantisation CPU effort
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3681-L3712)
---
## premultiply
```clojure
(premultiply input)
(premultiply input opts)
```
premultiply image alpha
Operation name: `premultiply`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3714-L3731)
---
## prewitt
```clojure
(prewitt input)
```
Prewitt edge detector
Operation name: `prewitt`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3733-L3749)
---
## profile
```clojure
(profile input)
```
find image profiles
Operation name: `profile`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `columns` -> `:columns` (image): First non-zero pixel in column
- `rows` -> `:rows` (image): First non-zero pixel in row
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3751-L3768)
---
## project
```clojure
(project input)
```
find image projections
Operation name: `project`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `columns` -> `:columns` (image): Sums of columns
- `rows` -> `:rows` (image): Sums of rows
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3770-L3787)
---
## quadratic
```clojure
(quadratic input coeff)
```
resample an image with a quadratic transform
Operation name: `quadratic`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `coeff` -> `:coeff` (image): Coefficient matrix
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3789-L3806)
---
## rad2float
```clojure
(rad2float input)
```
unpack Radiance coding to float RGB
Operation name: `rad2float`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3808-L3824)
---
## rank
```clojure
(rank input width height index)
```
rank filter
Operation name: `rank`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
- `index` -> `:index` (integer): Select pixel at index
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3826-L3845)
---
## rawload
```clojure
(rawload filename width height bands)
(rawload filename width height bands opts)
```
load raw data from a file
Operation name: `rawload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `bands` -> `:bands` (integer): Number of bands in image
Optional inputs:
- `offset` -> `:offset` (integer): Offset in bytes from start of file
- `format` -> `:format` (keyword, see `ol.vips.enums/band-format`): Pixel format in image
- `interpretation` -> `:interpretation` (keyword, see `ol.vips.enums/interpretation`): Pixel interpretation
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3847-L3877)
---
## rawsave
```clojure
(rawsave input filename)
(rawsave input filename opts)
```
save image to raw file
Operation name: `rawsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3879-L3901)
---
## recomb
```clojure
(recomb input m)
```
linear recombination with matrix
Operation name: `recomb`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `m` -> `:m` (image): Matrix of coefficients
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3903-L3920)
---
## reduce
```clojure
(reduce input xshrink hshrink yshrink vshrink)
(reduce input xshrink hshrink yshrink vshrink opts)
```
reduce an image
Operation name: `reduce`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3922-L3945)
---
## reduceh
```clojure
(reduceh input xshrink hshrink)
(reduceh input xshrink hshrink opts)
```
shrink an image horizontally
Operation name: `reduceh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3947-L3968)
---
## reducev
```clojure
(reducev input yshrink vshrink)
(reducev input yshrink vshrink opts)
```
shrink an image vertically
Operation name: `reducev`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3970-L3991)
---
## relational
```clojure
(relational left right relational)
```
relational operation on two images
Operation name: `relational`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
- `relational` -> `:relational` (keyword, see `ol.vips.enums/operation-relational`): Relational to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L3993-L4011)
---
## relational-const
```clojure
(relational-const input relational c)
```
relational operations against a constant
Operation name: `relational_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `relational` -> `:relational` (keyword, see `ol.vips.enums/operation-relational`): Relational to perform
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4013-L4031)
---
## remainder
```clojure
(remainder left right)
```
remainder after integer division of two images
Operation name: `remainder`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4033-L4050)
---
## remainder-const
```clojure
(remainder-const input c)
```
remainder after integer division of an image and a constant
Operation name: `remainder_const`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `c` -> `:c` (seqable of number): Array of constants
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4052-L4069)
---
## remosaic
```clojure
(remosaic input old-str new-str)
```
rebuild an mosaiced image
Operation name: `remosaic`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `old-str` -> `:old-str` (string): Search for this string
- `new-str` -> `:new-str` (string): And swap for this string
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4071-L4089)
---
## replicate
```clojure
(replicate input across down)
```
replicate an image
Operation name: `replicate`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `across` -> `:across` (integer): Repeat this many times horizontally
- `down` -> `:down` (integer): Repeat this many times vertically
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4091-L4109)
---
## resize
```clojure
(resize input scale)
(resize input scale opts)
```
resize an image
Operation name: `resize`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `scale` -> `:scale` (float): Scale image by this factor
Optional inputs:
- `kernel` -> `:kernel` (keyword, see `ol.vips.enums/kernel`): Resampling kernel
- `gap` -> `:gap` (float): Reducing gap
- `centre` -> `:centre` (boolean): Use centre sampling convention
- `vscale` -> `:vscale` (float): Vertical scale image by this factor
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4111-L4134)
---
## rot
```clojure
(rot input angle)
```
rotate an image
Operation name: `rot`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle`): Angle to rotate image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4136-L4153)
---
## rot45
```clojure
(rot45 input)
(rot45 input opts)
```
rotate an image
Operation name: `rot45`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `angle` -> `:angle` (keyword, see `ol.vips.enums/angle45`): Angle to rotate image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4155-L4172)
---
## rotate
```clojure
(rotate input angle)
(rotate input angle opts)
```
rotate an image by a number of degrees
Operation name: `rotate`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `angle` -> `:angle` (float): Rotate clockwise by this many degrees
Optional inputs:
- `background` -> `:background` (seqable of number): Background value
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4174-L4196)
---
## round
```clojure
(round input round)
```
perform a round function on an image
Operation name: `round`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `round` -> `:round` (keyword, see `ol.vips.enums/operation-round`): Rounding operation to perform
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4198-L4215)
---
## s-rgb2-hsv
```clojure
(s-rgb2-hsv input)
```
transform sRGB to HSV
Operation name: `sRGB2HSV`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4217-L4233)
---
## s-rgb2sc-rgb
```clojure
(s-rgb2sc-rgb input)
```
convert an sRGB image to scRGB
Operation name: `sRGB2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4235-L4251)
---
## sc-rgb2-bw
```clojure
(sc-rgb2-bw input)
(sc-rgb2-bw input opts)
```
convert scRGB to BW
Operation name: `scRGB2BW`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4253-L4270)
---
## sc-rgb2-xyz
```clojure
(sc-rgb2-xyz input)
```
transform scRGB to XYZ
Operation name: `scRGB2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4272-L4288)
---
## sc-rgb2s-rgb
```clojure
(sc-rgb2s-rgb input)
(sc-rgb2s-rgb input opts)
```
convert scRGB to sRGB
Operation name: `scRGB2sRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `depth` -> `:depth` (integer): Output device space depth in bits
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4290-L4307)
---
## scale
```clojure
(scale input)
(scale input opts)
```
scale an image to uchar
Operation name: `scale`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `exp` -> `:exp` (float): Exponent for log scale
- `log` -> `:log` (boolean): Log scale
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4309-L4327)
---
## scharr
```clojure
(scharr input)
```
Scharr edge detector
Operation name: `scharr`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4329-L4345)
---
## sdf
```clojure
(sdf width height shape)
(sdf width height shape opts)
```
create an SDF image
Operation name: `sdf`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
- `shape` -> `:shape` (keyword, see `ol.vips.enums/sdf-shape`): SDF shape to create
Optional inputs:
- `r` -> `:r` (float): Radius
- `a` -> `:a` (seqable of number): Point a
- `b` -> `:b` (seqable of number): Point b
- `corners` -> `:corners` (seqable of number): Corner radii
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4347-L4369)
---
## sequential
```clojure
(sequential input)
(sequential input opts)
```
check sequential access
Operation name: `sequential`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `trace` -> `:trace` (boolean): Trace pixel requests
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4371-L4390)
---
## sharpen
```clojure
(sharpen input)
(sharpen input opts)
```
unsharp masking for print
Operation name: `sharpen`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `radius` -> `:radius` (integer): Radius of Gaussian
- `sigma` -> `:sigma` (float): Sigma of Gaussian
- `x1` -> `:x1` (float): Flat/jaggy threshold
- `y2` -> `:y2` (float): Maximum brightening
- `y3` -> `:y3` (float): Maximum darkening
- `m1` -> `:m1` (float): Slope for flat areas
- `m2` -> `:m2` (float): Slope for jaggy areas
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4392-L4415)
---
## shrink
```clojure
(shrink input xshrink hshrink yshrink vshrink)
(shrink input xshrink hshrink yshrink vshrink opts)
```
shrink an image
Operation name: `shrink`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (float): Horizontal shrink factor
- `hshrink` -> `:hshrink` (float): Horizontal shrink factor
- `yshrink` -> `:yshrink` (float): Vertical shrink factor
- `vshrink` -> `:vshrink` (float): Vertical shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4417-L4438)
---
## shrinkh
```clojure
(shrinkh input xshrink hshrink)
(shrinkh input xshrink hshrink opts)
```
shrink an image horizontally
Operation name: `shrinkh`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `xshrink` -> `:xshrink` (integer): Horizontal shrink factor
- `hshrink` -> `:hshrink` (integer): Horizontal shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4440-L4459)
---
## shrinkv
```clojure
(shrinkv input yshrink vshrink)
(shrinkv input yshrink vshrink opts)
```
shrink an image vertically
Operation name: `shrinkv`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `yshrink` -> `:yshrink` (integer): Vertical shrink factor
- `vshrink` -> `:vshrink` (integer): Vertical shrink factor
Optional inputs:
- `ceil` -> `:ceil` (boolean): Round-up output dimensions
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4461-L4480)
---
## sign
```clojure
(sign input)
```
unit vector of pixel
Operation name: `sign`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4482-L4498)
---
## similarity
```clojure
(similarity input)
(similarity input opts)
```
similarity transform of an image
Operation name: `similarity`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
Optional inputs:
- `scale` -> `:scale` (float): Scale by this factor
- `angle` -> `:angle` (float): Rotate clockwise by this many degrees
- `background` -> `:background` (seqable of number): Background value
- `odx` -> `:odx` (float): Horizontal output displacement
- `ody` -> `:ody` (float): Vertical output displacement
- `idx` -> `:idx` (float): Horizontal input displacement
- `idy` -> `:idy` (float): Vertical input displacement
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4500-L4523)
---
## sines
```clojure
(sines width height)
(sines width height opts)
```
make a 2D sine wave
Operation name: `sines`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
- `hfreq` -> `:hfreq` (float): Horizontal spatial frequency
- `vfreq` -> `:vfreq` (float): Vertical spatial frequency
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4525-L4545)
---
## smartcrop
```clojure
(smartcrop input width height)
(smartcrop input width height opts)
```
extract an area from an image
Operation name: `smartcrop`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `input` -> `:input` (image): Input image
- `width` -> `:width` (integer): Width of extract area
- `height` -> `:height` (integer): Height of extract area
Optional inputs:
- `interesting` -> `:interesting` (keyword, see `ol.vips.enums/interesting`): How to measure interestingness
- `premultiplied` -> `:premultiplied` (boolean): Input image already has premultiplied alpha
Outputs:
- `out` -> `:out` (image): Output image
- `attention-x` -> `:attention-x` (integer): Horizontal position of attention centre
- `attention-y` -> `:attention-y` (integer): Vertical position of attention centre
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4547-L4569)
---
## sobel
```clojure
(sobel input)
```
Sobel edge detector
Operation name: `sobel`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4571-L4587)
---
## spcor
```clojure
(spcor input ref)
```
spatial correlation
Operation name: `spcor`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `ref` -> `:ref` (image): Input reference image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4589-L4606)
---
## spectrum
```clojure
(spectrum input)
```
make displayable power spectrum
Operation name: `spectrum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4608-L4624)
---
## stats
```clojure
(stats input)
```
find many image stats
Operation name: `stats`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output array of statistics
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4626-L4642)
---
## stdif
```clojure
(stdif input width height)
(stdif input width height opts)
```
statistical difference
Operation name: `stdif`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
- `width` -> `:width` (integer): Window width in pixels
- `height` -> `:height` (integer): Window height in pixels
Optional inputs:
- `s0` -> `:s0` (float): New deviation
- `b` -> `:b` (float): Weight of new deviation
- `m0` -> `:m0` (float): New mean
- `a` -> `:a` (float): Weight of new mean
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4644-L4666)
---
## subsample
```clojure
(subsample input xfac yfac)
(subsample input xfac yfac opts)
```
subsample an image
Operation name: `subsample`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `xfac` -> `:xfac` (integer): Horizontal subsample factor
- `yfac` -> `:yfac` (integer): Vertical subsample factor
Optional inputs:
- `point` -> `:point` (boolean): Point sample
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4668-L4687)
---
## subtract
```clojure
(subtract left right)
```
subtract two images
Operation name: `subtract`.
Returns an image handle.
Required inputs:
- `left` -> `:left` (image): Left-hand image argument
- `right` -> `:right` (image): Right-hand image argument
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4689-L4706)
---
## sum
```clojure
(sum input)
```
sum an array of images
Operation name: `sum`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (seqable of image): Array of input images
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4708-L4724)
---
## svgload
```clojure
(svgload filename)
(svgload filename opts)
```
load SVG with rsvg
Operation name: `svgload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `dpi` -> `:dpi` (float): Render at this DPI
- `scale` -> `:scale` (float): Scale output by this factor
- `unlimited` -> `:unlimited` (boolean): Allow SVG of any size
- `stylesheet` -> `:stylesheet` (string): Custom CSS
- `high-bitdepth` -> `:high-bitdepth` (boolean): Enable scRGB 128-bit output (32-bit per channel)
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4726-L4755)
---
## switch
```clojure
(switch tests)
```
find the index of the first non-zero pixel in tests
Operation name: `switch`.
Returns an image handle.
Required inputs:
- `tests` -> `:tests` (seqable of image): Table of images to test
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4757-L4773)
---
## system
```clojure
(system cmd-format)
(system cmd-format opts)
```
run an external command
Operation name: `system`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `cmd-format` -> `:cmd-format` (string): Command to run
Optional inputs:
- `input` -> `:in` (seqable of image): Array of input images
- `in-format` -> `:in-format` (string): Format for input filename
- `out-format` -> `:out-format` (string): Format for output filename
- `cache` -> `:cache` (boolean): Cache this call
Outputs:
- `out` -> `:out` (image): Output image
- `log` -> `:log` (string): Command log
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4775-L4796)
---
## text
```clojure
(text text)
(text text opts)
```
make a text image
Operation name: `text`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `text` -> `:text` (string): Text to render
Optional inputs:
- `font` -> `:font` (string): Font to render with
- `width` -> `:width` (integer): Maximum image width in pixels
- `height` -> `:height` (integer): Maximum image height in pixels
- `align` -> `:align` (keyword, see `ol.vips.enums/align`): Align on the low, centre or high edge
- `justify` -> `:justify` (boolean): Justify lines
- `dpi` -> `:dpi` (integer): DPI to render at
- `spacing` -> `:spacing` (integer): Line spacing
- `fontfile` -> `:fontfile` (string): Load this font file
- `rgba` -> `:rgba` (boolean): Enable RGBA output
- `wrap` -> `:wrap` (keyword, see `ol.vips.enums/text-wrap`): Wrap lines on word or character boundaries
Outputs:
- `out` -> `:out` (image): Output image
- `autofit-dpi` -> `:autofit-dpi` (integer): DPI selected by autofit
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4798-L4825)
---
## thumbnail
```clojure
(thumbnail filename width)
(thumbnail filename width opts)
```
generate thumbnail from file
Operation name: `thumbnail`.
Returns an image handle.
Required inputs:
- `filename` -> `:filename` (string): Filename to read from
- `width` -> `:width` (integer): Size to this width
Optional inputs:
- `height` -> `:height` (integer): Size to this height
- `size` -> `:size` (keyword, see `ol.vips.enums/size`): Only upsize, only downsize, or both
- `no-rotate` -> `:no-rotate` (boolean): Don’t use orientation tags to rotate image upright
- `crop` -> `:crop` (keyword, see `ol.vips.enums/interesting`): Reduce to fill target rectangle, then crop
- `linear` -> `:linear` (boolean): Reduce in linear light
- `input-profile` -> `:input-profile` (string): Fallback input profile
- `output-profile` -> `:output-profile` (string): Fallback output profile
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `auto-rotate` -> `:auto-rotate` (boolean): Use orientation tags to rotate image upright
- `import-profile` -> `:import-profile` (string): Fallback import profile
- `export-profile` -> `:export-profile` (string): Fallback export profile
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4827-L4856)
---
## thumbnail-image
```clojure
(thumbnail-image input width)
(thumbnail-image input width opts)
```
generate thumbnail from image
Operation name: `thumbnail_image`.
Avoid for routine thumbnailing. Prefer `thumbnail` when you can start from a filename.For already-loaded images, use normal image operations such as `resize` unless you specifically need this escape hatch.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image argument
- `width` -> `:width` (integer): Size to this width
Optional inputs:
- `height` -> `:height` (integer): Size to this height
- `size` -> `:size` (keyword, see `ol.vips.enums/size`): Only upsize, only downsize, or both
- `no-rotate` -> `:no-rotate` (boolean): Don’t use orientation tags to rotate image upright
- `crop` -> `:crop` (keyword, see `ol.vips.enums/interesting`): Reduce to fill target rectangle, then crop
- `linear` -> `:linear` (boolean): Reduce in linear light
- `input-profile` -> `:input-profile` (string): Fallback input profile
- `output-profile` -> `:output-profile` (string): Fallback output profile
- `intent` -> `:intent` (keyword, see `ol.vips.enums/intent`): Rendering intent
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `auto-rotate` -> `:auto-rotate` (boolean): Use orientation tags to rotate image upright
- `import-profile` -> `:import-profile` (string): Fallback import profile
- `export-profile` -> `:export-profile` (string): Fallback export profile
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4858-L4889)
---
## tiffload
```clojure
(tiffload filename)
(tiffload filename opts)
```
load tiff from file
Operation name: `tiffload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `autorotate` -> `:autorotate` (boolean): Rotate image using orientation tag
- `subifd` -> `:subifd` (integer): Subifd index
- `unlimited` -> `:unlimited` (boolean): Remove all denial of service limits
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4891-L4920)
---
## tiffsave
```clojure
(tiffsave input filename)
(tiffsave input filename opts)
```
save image to tiff file
Operation name: `tiffsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `compression` -> `:compression` (keyword, see `ol.vips.enums/foreign-tiff-compression`): Compression for this file
- `q` -> `:Q` (integer): Q factor
- `predictor` -> `:predictor` (keyword, see `ol.vips.enums/foreign-tiff-predictor`): Compression prediction
- `tile` -> `:tile` (boolean): Write a tiled tiff
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `pyramid` -> `:pyramid` (boolean): Write a pyramidal tiff
- `miniswhite` -> `:miniswhite` (boolean): Use 0 for white in 1-bit images
- `bitdepth` -> `:bitdepth` (integer): Write as a 1, 2, 4 or 8 bit image
- `resunit` -> `:resunit` (keyword, see `ol.vips.enums/foreign-tiff-resunit`): Resolution unit
- `xres` -> `:xres` (float): Horizontal resolution in pixels/mm
- `yres` -> `:yres` (float): Vertical resolution in pixels/mm
- `bigtiff` -> `:bigtiff` (boolean): Write a bigtiff image
- `properties` -> `:properties` (boolean): Write a properties document to IMAGEDESCRIPTION
- `region-shrink` -> `:region-shrink` (keyword, see `ol.vips.enums/region-shrink`): Method to shrink regions
- `level` -> `:level` (integer): Deflate (1-9, default 6) or ZSTD (1-22, default 9) compression level
- `lossless` -> `:lossless` (boolean): Enable WEBP lossless mode
- `depth` -> `:depth` (keyword, see `ol.vips.enums/foreign-dz-depth`): Pyramid depth
- `subifd` -> `:subifd` (boolean): Save pyr layers as sub-IFDs
- `premultiply` -> `:premultiply` (boolean): Save with premultiplied alpha
- `rgbjpeg` -> `:rgbjpeg` (boolean): Output RGB JPEG rather than YCbCr
- `squash` -> `:squash` (boolean): Squash images down to 1 bit
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4922-L4966)
---
## tilecache
```clojure
(tilecache input)
(tilecache input opts)
```
cache an image as a set of tiles
Operation name: `tilecache`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `tile-width` -> `:tile-width` (integer): Tile width in pixels
- `tile-height` -> `:tile-height` (integer): Tile height in pixels
- `max-tiles` -> `:max-tiles` (integer): Maximum number of tiles to cache
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Expected access pattern
- `threaded` -> `:threaded` (boolean): Allow threaded access
- `persistent` -> `:persistent` (boolean): Keep cache between evaluations
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4968-L4990)
---
## tonelut
```clojure
(tonelut)
(tonelut opts)
```
build a look-up table
Operation name: `tonelut`.
Returns an image handle.
Required inputs:
- none
Optional inputs:
- `in-max` -> `:in-max` (integer): Size of LUT to build
- `out-max` -> `:out-max` (integer): Maximum value in output LUT
- `lb` -> `:Lb` (float): Lowest value in output
- `lw` -> `:Lw` (float): Highest value in output
- `ps` -> `:Ps` (float): Position of shadow
- `pm` -> `:Pm` (float): Position of mid-tones
- `ph` -> `:Ph` (float): Position of highlights
- `s` -> `:S` (float): Adjust shadows by this much
- `m` -> `:M` (float): Adjust mid-tones by this much
- `h` -> `:H` (float): Adjust highlights by this much
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L4992-L5018)
---
## transpose3d
```clojure
(transpose3d input)
(transpose3d input opts)
```
transpose3d an image
Operation name: `transpose3d`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `page-height` -> `:page-height` (integer): Height of each input page
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5020-L5037)
---
## uhdr2sc-rgb
```clojure
(uhdr2sc-rgb input)
```
transform uhdr to scRGB
Operation name: `uhdr2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5039-L5055)
---
## uhdrload
```clojure
(uhdrload filename)
(uhdrload filename opts)
```
load a UHDR image
Operation name: `uhdrload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5057-L5082)
---
## uhdrsave
```clojure
(uhdrsave input filename)
(uhdrsave input filename opts)
```
save image in UltraHDR format
Operation name: `uhdrsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `gainmap-scale-factor` -> `:gainmap-scale-factor` (integer): The scale factor of base image to gainmap image
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5084-L5108)
---
## unpremultiply
```clojure
(unpremultiply input)
(unpremultiply input opts)
```
unpremultiply image alpha
Operation name: `unpremultiply`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `max-alpha` -> `:max-alpha` (float): Maximum value of alpha channel
- `alpha-band` -> `:alpha-band` (integer): Unpremultiply with this alpha
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5110-L5128)
---
## vipsload
```clojure
(vipsload filename)
(vipsload filename opts)
```
load vips from file
Operation name: `vipsload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5130-L5154)
---
## vipssave
```clojure
(vipssave input filename)
(vipssave input filename opts)
```
save image to file in vips format
Operation name: `vipssave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5156-L5178)
---
## webpload
```clojure
(webpload filename)
(webpload filename opts)
```
load webp from file
Operation name: `webpload`.
Returns a closeable result map. Pass it anywhere an image is expected, or inspect `:out` and the additional outputs directly.
Required inputs:
- `filename` -> `:filename` (string): Filename to load from
Optional inputs:
- `page` -> `:page` (integer): First page to load
- `n` -> `:n` (integer): Number of pages to load, -1 for all
- `scale` -> `:scale` (float): Factor to scale by
- `shrink` -> `:shrink` (integer): Shrink factor on load
- `memory` -> `:memory` (boolean): Force open via memory
- `access` -> `:access` (keyword, see `ol.vips.enums/access`): Required access pattern for this file
- `fail-on` -> `:fail-on` (keyword, see `ol.vips.enums/fail-on`): Error level to fail on
- `revalidate` -> `:revalidate` (boolean): Don’t use a cached result for this operation
- `sequential` -> `:sequential` (boolean): Sequential read only
- `fail` -> `:fail` (boolean): Fail on first warning
- `disc` -> `:disc` (boolean): Open to disc
Outputs:
- `out` -> `:out` (image): Output image
- `flags` -> `:flags` (integer flags): Flags for this file
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5180-L5208)
---
## webpsave
```clojure
(webpsave input filename)
(webpsave input filename opts)
```
save as WebP
Operation name: `webpsave`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
- `filename` -> `:filename` (string): Filename to save to
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `exact` -> `:exact` (boolean): Preserve color values from transparent pixels
- `preset` -> `:preset` (keyword, see `ol.vips.enums/foreign-webp-preset`): Preset for lossy compression
- `smart-subsample` -> `:smart-subsample` (boolean): Enable high quality chroma subsampling
- `near-lossless` -> `:near-lossless` (boolean): Enable preprocessing in lossless mode (uses Q)
- `alpha-q` -> `:alpha-q` (integer): Change alpha plane fidelity for lossy compression
- `min-size` -> `:min-size` (boolean): Optimise for minimum size
- `kmin` -> `:kmin` (integer): Minimum number of frames between key frames
- `kmax` -> `:kmax` (integer): Maximum number of frames between key frames
- `effort` -> `:effort` (integer): Level of CPU effort to reduce file size
- `target-size` -> `:target-size` (integer): Desired target size in bytes
- `reduction-effort` -> `:reduction-effort` (integer): Level of CPU effort to reduce file size
- `mixed` -> `:mixed` (boolean): Allow mixed encoding (might reduce file size)
- `smart-deblock` -> `:smart-deblock` (boolean): Enable auto-adjusting of the deblocking filter
- `passes` -> `:passes` (integer): Number of entropy-analysis passes (in [1..10])
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5210-L5248)
---
## webpsave-mime
```clojure
(webpsave-mime input)
(webpsave-mime input opts)
```
save image to webp mime
Operation name: `webpsave_mime`.
Returns a result map.
Required inputs:
- `input` -> `:in` (image): Image to save
Optional inputs:
- `q` -> `:Q` (integer): Q factor
- `lossless` -> `:lossless` (boolean): Enable lossless compression
- `exact` -> `:exact` (boolean): Preserve color values from transparent pixels
- `preset` -> `:preset` (keyword, see `ol.vips.enums/foreign-webp-preset`): Preset for lossy compression
- `smart-subsample` -> `:smart-subsample` (boolean): Enable high quality chroma subsampling
- `near-lossless` -> `:near-lossless` (boolean): Enable preprocessing in lossless mode (uses Q)
- `alpha-q` -> `:alpha-q` (integer): Change alpha plane fidelity for lossy compression
- `min-size` -> `:min-size` (boolean): Optimise for minimum size
- `kmin` -> `:kmin` (integer): Minimum number of frames between key frames
- `kmax` -> `:kmax` (integer): Maximum number of frames between key frames
- `effort` -> `:effort` (integer): Level of CPU effort to reduce file size
- `target-size` -> `:target-size` (integer): Desired target size in bytes
- `reduction-effort` -> `:reduction-effort` (integer): Level of CPU effort to reduce file size
- `mixed` -> `:mixed` (boolean): Allow mixed encoding (might reduce file size)
- `smart-deblock` -> `:smart-deblock` (boolean): Enable auto-adjusting of the deblocking filter
- `passes` -> `:passes` (integer): Number of entropy-analysis passes (in [1..10])
- `keep` -> `:keep` (integer flags): Which metadata to retain
- `background` -> `:background` (seqable of number): Background value
- `page-height` -> `:page-height` (integer): Set page height for multipage save
- `profile` -> `:profile` (string): Filename of ICC profile to embed
- `strip` -> `:strip` (boolean): Strip all metadata from image
Outputs:
- none
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5250-L5287)
---
## worley
```clojure
(worley width height)
(worley width height opts)
```
make a worley noise image
Operation name: `worley`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `cell-size` -> `:cell-size` (integer): Size of Worley cells
- `seed` -> `:seed` (integer): Random number seed
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5289-L5308)
---
## wrap
```clojure
(wrap input)
(wrap input opts)
```
wrap image origin
Operation name: `wrap`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `x` -> `:x` (integer): Left edge of input in output
- `y` -> `:y` (integer): Top edge of input in output
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5310-L5328)
---
## xyz
```clojure
(xyz width height)
(xyz width height opts)
```
make an image where pixel values are coordinates
Operation name: `xyz`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `csize` -> `:csize` (integer): Size of third dimension
- `dsize` -> `:dsize` (integer): Size of fourth dimension
- `esize` -> `:esize` (integer): Size of fifth dimension
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5330-L5350)
---
## xyz2-cmyk
```clojure
(xyz2-cmyk input)
```
transform XYZ to CMYK
Operation name: `XYZ2CMYK`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5352-L5368)
---
## xyz2-lab
```clojure
(xyz2-lab input)
(xyz2-lab input opts)
```
transform XYZ to Lab
Operation name: `XYZ2Lab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- `temp` -> `:temp` (seqable of number): Colour temperature
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5370-L5387)
---
## xyz2-oklab
```clojure
(xyz2-oklab input)
```
transform XYZ to Oklab
Operation name: `XYZ2Oklab`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5389-L5405)
---
## xyz2-yxy
```clojure
(xyz2-yxy input)
```
transform XYZ to Yxy
Operation name: `XYZ2Yxy`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5407-L5423)
---
## xyz2sc-rgb
```clojure
(xyz2sc-rgb input)
```
transform XYZ to scRGB
Operation name: `XYZ2scRGB`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5425-L5441)
---
## yxy2-xyz
```clojure
(yxy2-xyz input)
```
transform Yxy to XYZ
Operation name: `Yxy2XYZ`.
Returns an image handle.
Required inputs:
- `input` -> `:in` (image): Input image
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5443-L5459)
---
## zone
```clojure
(zone width height)
(zone width height opts)
```
make a zone plate
Operation name: `zone`.
Returns an image handle.
Required inputs:
- `width` -> `:width` (integer): Image width in pixels
- `height` -> `:height` (integer): Image height in pixels
Optional inputs:
- `uchar` -> `:uchar` (boolean): Output an unsigned char image
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5461-L5479)
---
## zoom
```clojure
(zoom input xfac yfac)
```
zoom an image
Operation name: `zoom`.
Returns an image handle.
Required inputs:
- `input` -> `:input` (image): Input image
- `xfac` -> `:xfac` (integer): Horizontal zoom factor
- `yfac` -> `:yfac` (integer): Vertical zoom factor
Optional inputs:
- none
Outputs:
- `out` -> `:out` (image): Output image
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips/operations.clj#L5481-L5499)
## ol.vips
# ol.vips
Load, transform, and write images with libvips from Clojure.
`ol.vips` exposes the runtime, I/O, metadata, and animated-image helpers that
most applications use directly. Loaders such as [`from-file`](#from-file),
[`from-buffer`](#from-buffer), and [`from-stream`](#from-stream) return closeable image handles.
Operations return new image handles rather than mutating the original image,
and sinks such as [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), and
[`write-to-stream`](#write-to-stream) trigger evaluation of the underlying libvips pipeline.
Guides and API reference live at https://docs.outskirtslabs.com/ol.vips/next/.
Use this namespace for:
* runtime initialization and safety controls
* file, buffer, and stream input/output
* metadata and raw header access
* animated-image metadata and frame-aware helpers
* low-level generic operation calls with [`call`](#call)
## Related Namespaces
* [`ol.vips.operations`](api/ol-vips-operations.adoc) for generated wrappers around the libvips operation surface
* [`ol.vips.enums`](api/ol-vips-enums.adoc) for normalized enum keywords and enum value sets
## Example
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/init!)
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300 {:auto-rotate true})
rotated (ops/rotate thumb 90.0)]
(v/write-to-file rotated "thumbnail.jpg")
(v/metadata rotated))
;; => {:width 300, :height 242, :bands 3, :has-alpha? false}
```
## init!
```clojure
(init!)
```
Starts libvips and loads the `ol.vips` native bindings for this process.
This function is idempotent. Most public API calls initialize libvips
lazily, so call `init!` when you want eager startup or want to inspect the
shared runtime state up front.
On first initialization, `ol.vips` restores the secure default and blocks
libvips operations marked as untrusted. See
[`set-block-untrusted-operations!`](#set-block-untrusted-operations!).
Returns the shared runtime state map, which should be considered an opaque handle and is not public API.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L47-L60)
---
## set-block-untrusted-operations!
```clojure
(set-block-untrusted-operations! blocked?)
```
Set whether libvips operations tagged as untrusted are blocked at runtime.
This is the direct wrapper around libvips `vips_block_untrusted_set`.
Pass `blocked?` as `true` to restore the secure default and block operations
libvips has marked as untrusted. Pass `false` to allow them to run.
Returns the current runtime state map.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L62-L71)
---
## set-operation-block!
```clojure
(set-operation-block! name blocked?)
```
Set the block state for a libvips operation class hierarchy.
`name` should be a libvips operation class name such as `"VipsForeignLoad"`
or `"VipsForeignLoadJpeg"`. libvips applies the block state at that point in
the class hierarchy and to all descendants. This is the direct wrapper around
libvips `vips_operation_block_set`.
Pass `blocked?` as `true` to block that class hierarchy, or `false` to allow
it.
Example:
```clojure
(v/set-operation-block! "VipsForeignLoad" true)
(v/set-operation-block! "VipsForeignLoadJpeg" false)
```
That blocks all loaders except the JPEG loader family.
Returns `{:name :blocked? }`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L73-L95)
---
## operation-cache-settings
```clojure
(operation-cache-settings)
```
Returns the current libvips operation cache settings.
Because libvips operations are free of side effects, libvips can cache a
previous call to the same operation with the same arguments and return the
previous result again.
Returns `{:max :size :max-mem :max-files }`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L97-L106)
---
## set-operation-cache-max!
```clojure
(set-operation-cache-max! max)
```
Sets the maximum number of operations libvips keeps in the operation cache.
Reducing this limit may trim cached operations immediately.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L108-L115)
---
## set-operation-cache-max-mem!
```clojure
(set-operation-cache-max-mem! max-mem)
```
Sets the maximum amount of tracked memory, in bytes, libvips allows before
it starts dropping cached operations.
libvips only tracks memory it allocates itself. Memory allocated by external
libraries is not included.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L117-L126)
---
## set-operation-cache-max-files!
```clojure
(set-operation-cache-max-files! max-files)
```
Sets the maximum number of tracked files libvips allows before it starts
dropping cached operations.
libvips only tracks file descriptors it opens itself. Descriptors opened by
external libraries are not included.
Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L128-L137)
---
## disable-operation-cache!
```clojure
(disable-operation-cache!)
```
Disables the libvips operation cache by setting the maximum cached
operation count to `0`.
This is often useful for image-proxy style workloads that process many
different images.
Returns the current cache settings map. See [`set-operation-cache-max!`](#set-operation-cache-max!).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L139-L148)
---
## tracked-resources
```clojure
(tracked-resources)
```
Returns the current libvips tracked resource counters.
libvips uses these counters to decide when to start dropping cached
operations. The returned map has these keys:
| | |
| --- | --- |
| key | description |
| `:mem` | Bytes currently allocated via libvips tracked allocators |
| `:mem-highwater` | Largest tracked allocation total seen so far |
| `:allocs` | Number of active tracked allocations |
| `:files` | Number of tracked open files |
These counters only include resources libvips tracks itself.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L150-L165)
---
## operations
```clojure
(operations)
```
Returns the sorted libvips operation nicknames known to the runtime.
These are the names accepted by [`call`](#call) and by [`operation-info`](#operation-info), such as
`"flip"`, `"rotate"`, or `"thumbnail_image"`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L167-L173)
---
## operation-info
```clojure
(operation-info operation-name)
```
Describes a libvips operation by nickname.
Returns a map with the operation `:name`, a short `:description`, and an
`:args` vector describing each argument’s name, blurb, type, and whether it
is an input, output, or required argument.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L175-L182)
---
## encode-enum
```clojure
(encode-enum enum-type-name value)
```
Encodes a Clojure enum value for a libvips enum type.
`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.
Pass a keyword like `:horizontal` or a valid enum integer value.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L184-L190)
---
## decode-enum
```clojure
(decode-enum enum-type-name value)
```
Decodes a libvips enum integer to a normalized Clojure keyword.
`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.
```clojure
(v/decode-enum "VipsDirection"
(v/encode-enum "VipsDirection" :horizontal))
;;=> :horizontal
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L192-L203)
---
## call
```clojure
(call operation-name opts)
```
Calls a libvips operation by nickname.
This is the low-level generic binding API described by libvips for language
bindings: create an operation from its nickname, set properties, execute with
the operation cache, then extract outputs.
`operation-name` should be a libvips operation nickname such as `"flip"` or
`"embed"`. `opts` keys should match libvips argument names as keywords.
Enum arguments accept the normalized keywords used by [`encode-enum`](#encode-enum). Image
arguments accept image handles and prior operation result maps with `:out`.
Returns the operation outputs in the most useful shape for Clojure:
* If the only output is an image at `:out`, returns that image handle.
* Otherwise returns a map of outputs.
* Returned output maps that hold closeable image values are themselves
closeable.
Example:
```clojure
(v/call "flip" {:in image
:direction :horizontal})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L205-L231)
---
## from-file
```clojure
(from-file path)
(from-file path opts)
```
Opens an image from `path` and returns a closeable image handle.
`path` can be a string, `java.nio.file.Path`, `java.io.File`, or another
`CharSequence`. Arity 2 appends `opts` in libvips option-string form. The
available option keys depend on the loader libvips selects for that path.
To discover loader-specific options like `:shrink`, see the generated
wrappers in [`ol.vips.operations`](api/ol-vips-operations.adoc), for example
[`ol.vips.operations/jpegload`](api/ol-vips-operations.adoc#jpegload) or [`ol.vips.operations/pngload`](api/ol-vips-operations.adoc#pngload). For enum
option values like `:sequential` on `:access`, see [`ol.vips.enums/access`](api/ol-vips-enums.adoc#access)
or [`ol.vips.enums/describe`](api/ol-vips-enums.adoc#describe).
```clojure
(v/from-file "input.jpg" {:access :sequential
:shrink 2})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L233-L253)
---
## write-to-file
```clojure
(write-to-file image path)
(write-to-file image path opts)
```
Writes `image` to `path` and returns `image`.
libvips infers the saver from the path or extension. Arity 3 appends
`opts` in libvips option-string form. The available option keys depend on
the saver libvips selects for that path.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave). For enum option values, see
[`ol.vips.enums`](api/ol-vips-enums.adoc).
```clojure
(v/write-to-file image "output.png" {:compression 9})
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L255-L273)
---
## from-buffer
```clojure
(from-buffer source)
(from-buffer source opts)
```
Opens an image from in-memory bytes and returns a closeable image handle.
`source` may be a byte array or a byte sequence. Arity 2 passes `opts` as a
libvips option string for the loader.
The available option keys depend on the loader that recognizes the input
bytes. See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums`](api/ol-vips-enums.adoc) for enum value sets.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L275-L287)
---
## from-stream
```clojure
(from-stream is)
(from-stream is opts)
```
Opens an image from an `InputStream` and returns a closeable image handle.
The stream is bridged to a libvips source and is closed when the returned
image handle is closed. Arity 2 passes `opts` as a libvips option string for
the loader.
This is useful for streaming or non-file inputs, especially together with
loader hints like `:access :sequential`.
The available option keys depend on the loader that recognizes the stream.
See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums/access`](api/ol-vips-enums.adoc#access) for valid `:access` values.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L289-L305)
---
## write-to-buffer
```clojure
(write-to-buffer image suffix)
(write-to-buffer image suffix opts)
```
Encodes `image` and returns the result as a byte array.
`suffix` selects the saver and can include libvips save options directly,
such as `".png"` or `".png[compression=9]"`. Arity 3 appends `opts` to
`suffix` in libvips option-string form.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L307-L320)
---
## write-to-stream
```clojure
(write-to-stream image os suffix)
(write-to-stream image os suffix opts)
```
Encodes `image` to an `OutputStream` and returns `image`.
`suffix` selects the saver, for example `".png"` or `".jpg"`. Arity 4
appends `opts` to `suffix` in libvips option-string form.
The stream is flushed and closed after the write completes, and is also
closed if the write fails.
To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L322-L337)
---
## metadata
```clojure
(metadata image)
```
Returns a curated metadata map for `image`.
This is the high-level metadata view for the public API. It includes the core
image header fields such as width, height, bands, format, coding,
interpretation, resolution, offsets, and `:has-alpha?`, plus common animated
image fields when present such as `:pages`, `:page-height`, `:loop`, and
`:delay`.
For raw header and metadata access by libvips field name, see [`field`](#field),
[`field-names`](#field-names), and [`headers`](#headers).
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L339-L351)
---
## field
```clojure
(field image field-name)
(field image field-name not-found)
```
Returns the libvips header or metadata field named by `field-name`.
Items of metadata are identified by strings. Use this for direct access to
libvips header fields and attached metadata such as `"xres"`,
`"icc-profile-data"`, or `"delay"`.
Arity 2 returns `nil` for missing fields. Arity 3 returns `not-found`
instead.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L353-L365)
---
## field-as-string
```clojure
(field-as-string image field-name)
(field-as-string image field-name not-found)
```
Returns the libvips field named by `field-name` rendered as a string.
This is the direct string form of a header or metadata field, useful for
display and debugging. Arity 3 returns `not-found` when the field is
absent.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L367-L376)
---
## field-names
```clojure
(field-names image)
```
Returns the libvips field names attached to `image`.
This includes both core header fields and attached metadata field names.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L378-L383)
---
## headers
```clojure
(headers image)
```
Returns the raw libvips header and metadata map for `image`.
Keys are the original libvips field names as strings. Prefer [`metadata`](#metadata)
when you want the higher-level normalized public view.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L385-L391)
---
## has-field?
```clojure
(has-field? image field-name)
```
Returns `true` if `image` has the libvips field named by `field-name`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L393-L396)
---
## width
```clojure
(width image)
```
Returns the image width in pixels.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L398-L401)
---
## height
```clojure
(height image)
```
Returns the image height in pixels.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L403-L406)
---
## bands
```clojure
(bands image)
```
Returns the number of image bands as an integer.
libvips images have three dimensions: width, height, and bands.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L408-L413)
---
## has-alpha?
```clojure
(has-alpha? image)
```
Returns `true` if `image` has an alpha band.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L415-L418)
---
## shape
```clojure
(shape image)
```
Returns `[width height bands]` for `image`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L420-L425)
---
## copy-memory
```clojure
(copy-memory image)
```
Materializes `image` into a private in-memory `VipsImage` and returns it as a
normal closeable image handle.
This is effectively a sink. Calling `copy-memory` forces libvips to evaluate
the current pipeline now, render the pixels into memory, and wrap that memory
in a new image handle. Unlike [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), or
[`write-to-stream`](#write-to-stream), the result is still an image you can keep using in
downstream image operations.
This is useful when an intermediate image will be reused several times and you
do not want libvips to recompute the upstream pipeline for each branch. It is
also the explicit way to ask for a private memory-backed image before applying
mutating draw operations.
Behavior:
* Preserves the rendered image pixels and image metadata.
* Returns a new handle that remains usable after the source pipeline handles
have been closed.
* May avoid an extra copy if libvips determines the input is already a simple
readable memory image, in which case it can return another reference to the
existing image instead of allocating again.
* Trades CPU savings for higher memory use, so it is best reserved for
intermediates you know are reused often enough to justify retaining the
pixels.
Example:
```clojure
(with-open [base (v/from-file "input.jpg")
step (-> base
(ops/resize 0.5)
(ops/sharpen))
cached (v/copy-memory step)]
(do-something cached)
(do-something-else cached))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L427-L466)
---
## assoc-field
```clojure
(assoc-field image field-name value)
(assoc-field image field-name value opts)
```
Returns a new image with the libvips field named by `field-name` set to `value`.
`assoc-field` is immutable at the API level: it leaves `image` unchanged and
returns a new image handle with the updated header or metadata field.
Arity 4 accepts an options map.
Options:
| | |
| --- | --- |
| key | description |
| `:type` | Explicit libvips field type to use when `value` is ambiguous, such as `:array-int` for integer vectors |
Header fields such as `"xres"` and `"yres"` are updated as real libvips
header values so they survive save and reload. Custom metadata fields are
copied onto a fresh image header and can be read back with [`field`](#field) or
[`headers`](#headers).
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (-> image
(v/assoc-field "xres" 10.0)
(v/assoc-field "delay" [10 20 30] {:type :array-int}))]
[(v/field tagged "xres")
(v/field tagged "delay")])
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L468-L500)
---
## update-field
```clojure
(update-field image field-name f & args)
```
Returns a new image with `field-name` updated by applying `f` to its current value.
This is the functional update variant of [`assoc-field`](#assoc-field). The current field
value is read with [`field`](#field) and passed to `f` along with any extra `args`.
The result becomes the new field value on the returned image.
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (v/assoc-field image "custom-int" 42)
updated (v/update-field tagged "custom-int" inc)]
(v/field updated "custom-int"))
;; => 43
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L502-L519)
---
## dissoc-field
```clojure
(dissoc-field image field-name)
```
Returns a new image with the libvips field named by `field-name` removed.
This leaves `image` unchanged and removes the field from a copied image
header. It is most useful for stripping attached metadata fields that should
not be preserved downstream.
Example:
```clojure
(with-open [image (v/from-file "input.jpg")
tagged (v/assoc-field image "custom-string" "hello")
stripped (v/dissoc-field tagged "custom-string")]
(v/field stripped "custom-string"))
;; => nil
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L521-L538)
---
## pages
```clojure
(pages image)
```
Returns the animated page count from `image`, or `nil` when it is absent.
This reads the libvips `"n-pages"` field directly. For ordinary single-page
images that field is usually missing, so `pages` commonly returns `nil`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L540-L546)
---
## page-height
```clojure
(page-height image)
```
Returns the per-frame height for an animated image, or `nil` when it is absent.
libvips stores animated images as frames stacked vertically in one image.
`page-height` is the height of each logical frame.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L548-L554)
---
## page-delays
```clojure
(page-delays image)
```
Returns the animated frame delay vector from `image`, or `nil` when it is absent.
Delay values are returned as integers in the same order as the frames.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L556-L561)
---
## loop-count
```clojure
(loop-count image)
```
Returns the animated loop count from `image`, or `nil` when it is absent.
A loop count of `0` means loop forever when the target format supports that
convention.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L563-L569)
---
## assoc-pages
```clojure
(assoc-pages image page-count)
```
Returns a new image with animated page count metadata set to `page-count`.
`page-count` must be a positive integer. This updates the libvips
`"n-pages"` field on a copied image header and leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L571-L577)
---
## assoc-page-height
```clojure
(assoc-page-height image frame-height)
```
Returns a new image with animated frame height metadata set to `frame-height`.
`frame-height` must be a positive integer. This updates the libvips
`"page-height"` field on a copied image header and leaves `image`
unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L579-L586)
---
## assoc-page-delays
```clojure
(assoc-page-delays image delays)
```
Returns a new image with animated frame delay metadata set to `delays`.
`delays` must be a non-empty sequence of integers. When `image` already has
an explicit page count, the number of delay entries must match it.
This writes the libvips `"delay"` field with the correct array type and
leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L588-L597)
---
## assoc-loop-count
```clojure
(assoc-loop-count image loop-value)
```
Returns a new image with animated loop count metadata set to `loop-value`.
`loop-value` must be a non-negative integer. `0` means loop forever for
formats that use that convention. This leaves `image` unchanged.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L599-L605)
---
## extract-area-pages
```clojure
(extract-area-pages image left top width height)
```
Extracts the same rectangle from each frame of `image`.
For animated images, this crops every logical frame independently and returns
a reassembled animated image with `:pages`, `:page-height`, `:loop`, and
`:delay` preserved. For ordinary single-page images, this behaves like the
normal libvips `extract_area` operation.
Example:
```clojure
(with-open [image (ops/gifload "input.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)]
(select-keys (v/metadata cropped) [:width :height :pages :page-height]))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L607-L623)
---
## embed-pages
```clojure
(embed-pages image x y width height)
(embed-pages image x y width height opts)
```
Embeds each frame of `image` into a new canvas and returns the result.
For animated images, this applies libvips `embed` to every frame and then
reassembles the result while preserving animation metadata such as `:loop`
and `:delay`. For ordinary single-page images, this behaves like the normal
libvips `embed` operation.
Arity 6 accepts the same options map passed to libvips `embed`.
Options:
| | |
| --- | --- |
| key | description |
| `:extend` | How pixels outside the source image are filled, for example `:background` |
| `:background` | Background band values used when `:extend` is `:background` |
Example:
```clojure
(with-open [image (ops/gifload "input.gif" {:n -1})
framed (v/embed-pages image 8 8 70 70 {:extend :background
:background [0 0 0 0]})]
(v/page-height framed))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L625-L653)
---
## rot-pages
```clojure
(rot-pages image angle)
```
Rotates each frame of `image` by `angle`.
For animated images, this applies libvips `rot` to each frame and
reassembles the result with updated frame geometry and preserved animation
metadata. For ordinary single-page images, this behaves like the normal
libvips `rot` operation.
`angle` accepts the normalized enum keywords from [`ol.vips.enums/angle`](api/ol-vips-enums.adoc#angle),
such as `:d90`, `:d180`, or `:d270`.
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L655-L666)
---
## assemble-pages
```clojure
(assemble-pages frames)
(assemble-pages frames {:keys [loop delay] :as _opts})
```
Stacks `frames` into one animated image and annotates the result as multi-page.
`frames` must be a non-empty collection of images. Each input image becomes
one animation frame. All frames must have the same width and height. The
returned image has `:pages` set to the number of frames and `:page-height`
set to the height of each input frame.
Arity 2 accepts an options map.
Options:
| | |
| --- | --- |
| key | description |
| `:loop` | Animated loop count as a non-negative integer |
| `:delay` | Non-empty sequence of integer frame delays, one per frame |
`assemble-pages` requires at least one frame. When `:delay` is provided, its
entry count must match the number of frames.
Example:
```clojure
(with-open [animated (v/assemble-pages [frame-a frame-b frame-c]
{:loop 2
:delay [80 120 160]})]
(select-keys (v/metadata animated) [:pages :page-height :loop :delay]))
```
[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L668-L699)
## Changelog
# Changelog
All notable changes to this project will be documented in this file.
This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning).
## UNRELEASED
## `v0.0.1` (2026-07-13)
* Released the first public version of the `ol.vips` main library.
* Added a Clojure-owned FFI/FFM layer with generated operation and enum
wrappers, image I/O for files, byte arrays, and streams, metadata helpers,
and multipage and animated-image operations.
* Published native companion jars for Linux on x86-64 and AArch64 with glibc
or musl, macOS on x86-64 and Apple silicon, and Windows on x86-64.
* Updated every native companion jar to `sharp-libvips` 1.3.2, which bundles
libvips 8.18.3.
* Regenerated the operation and enum bindings for libvips 8.18.3, including
Oklab, Oklch, and UltraHDR support exposed by the upstream binaries.
* Added publication checks that validate every staged native bundle against
the project’s declared libvips version.
Please report any problems and let me know if anything is unclear or inconvenient. Thank you.
## Examples
# Examples
The snippets below show the main API shapes inline. For runnable
end-to-end scripts, see the examples in [`examples/`](https://github.com/outskirtslabs/vips/tree/main/examples):
* [`examples/create_thumbnail.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/create_thumbnail.clj) creates a thumbnail from a local file and writes it back out.
* [`examples/chain_transforms.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/chain_transforms.clj) chains several image operations together with the threading style API.
* [`examples/compose_images.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/compose_images.clj) joins two images and builds a small grid with `join` and `arrayjoin`.
* [`examples/bytes_and_streams.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/bytes_and_streams.clj) reads image bytes into memory, streams them through libvips, and writes the result back out.
* [`examples/http_stream_fetch.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/http_stream_fetch.clj) streams an HTTP response body into libvips and saves a transformed result.
* [`examples/image_diff.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/image_diff.clj) compares two same-sized images, thresholds the absolute difference, and composites a red highlight overlay onto the changed pixels.
* [`examples/palette_extractor.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/palette_extractor.clj) extracts the top dominant colors from an image with `hist_find_ndim`, prints channel stats from `stats`, and renders a swatch strip below the source image.
* [`examples/metadata_roundtrip.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/metadata_roundtrip.clj) reads and writes metadata fields before saving an image.
* [`examples/animated_gif.clj`](https://github.com/outskirtslabs/vips/blob/main/examples/animated_gif.clj) loads every GIF frame, transforms each page, and writes an animated result.
## File Input And Output
Use the file helpers when your source and destination already live on
disk and you want libvips to infer formats from the path or suffix.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg" {:shrink 2})]
(v/metadata image)
(v/shape image)
(v/write-to-file image "rabbit.png" {:compression 9})
(v/write-to-buffer image ".png" {:compression 9}))
```
## Metadata
`ol.vips` exposes a curated `v/metadata` summary and a generic header
API for libvips metadata fields.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")]
{:meta (v/metadata image)
:width (v/field image "width")
:fields (take 5 (v/field-names image))})
;; => {:meta {:width 2490, :height 3084, :bands 3, :has-alpha? false}
;; :width 2490
;; :fields ("width" "height" "bands" "format" "coding")}
```
Immutable metadata edits use `assoc`/`update`/`dissoc` style names.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
tagged (-> image
(v/assoc-field "xres" 10.0)
(v/update-field "yres" (constantly 10.0)))]
(v/write-to-file tagged "rabbit-copy.jpg" {:strip false})
(select-keys (v/headers tagged) ["width" "height" "xres" "yres"]))
```
## Animated Images
Load every page or frame with a format-specific loader and
`{:n -1}`. libvips represents animated and multipage images as one
tall strip plus metadata such as `n-pages`, `page-height`, `loop`, and
`delay`.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)
turned (v/rot-pages cropped :d90)
looped (v/assoc-loop-count turned 2)]
(v/write-to-file looped "cogs-turned.gif")
(v/metadata looped))
;; => {:width 50, :height 250, :bands 4, :has-alpha? true, :pages 5, ...}
```
The dedicated animated helpers are:
* `v/pages`, `v/page-height`, `v/page-delays`, and `v/loop-count`
* `v/assoc-pages`, `v/assoc-page-height`, `v/assoc-page-delays`, and
`v/assoc-loop-count`
* `v/extract-area-pages`, `v/embed-pages`, `v/rot-pages`, and
`v/assemble-pages`
These helpers work best for uniform-height multipage inputs such as
animated GIF/WebP strips and similarly-shaped TIFF/PDF inputs.
For a deeper guide, see
[Multipage and Animated Images](https://docs.outskirtslabs.com/ol.vips/next/multipage-and-animated-images).
## Image Loaders
The generic loaders `v/from-file`, `v/from-buffer`, and `v/from-stream`
auto-detect the input format and accept option maps that libvips
forwards to the selected loader. When you need format-specific behavior,
use the generated loader operations in `ol.vips.operations`, such as
`ops/jpegload`.
`ol.vips` blocks libvips operations marked as untrusted by default. This can
block loaders with a larger attack surface, such as PDF or ImageMagick-backed
formats.
If you trust your input and need those operations, opt in explicitly
before loading that data:
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/set-block-untrusted-operations! false)
(with-open [image (ops/pdfload "document.pdf")]
(v/metadata image))
```
Use that override only for trusted inputs. See the upstream
https://www.libvips.org/API/8.18/func.block_untrusted_set.html[`vips_block_untrusted_set`
documentation]. In libvips terms this is equivalent to calling
`vips_block_untrusted_set(FALSE)` after initialization.
JPEG auto-rotation rotates the image according to its EXIF orientation
at load time:
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/jpegload "dev/rabbit.jpg" {:autorotate true})]
(v/write-to-file image "rabbit-upright.jpg")
(v/metadata image))
```
## Streaming Input And Output
`from-stream` reads from any `java.io.InputStream`. `from-buffer` reads
from a byte array already in memory.
```clojure
(require '[babashka.http-client :as http]
'[ol.vips :as v]
'[ol.vips.operations :as ops])
(import '[java.io FileOutputStream])
(with-open [response-body (:body (http/get "https://casey.link/square-flask.png"
{:as :stream}))
image (v/from-stream response-body {:access :sequential
:fail-on :error})
thumbnail (ops/thumbnail-image image 200)
out (FileOutputStream. "square-flask-thumb.png")]
(v/write-to-stream thumbnail out ".png")
(v/metadata thumbnail))
```
`write-to-stream` writes encoded bytes to any `java.io.OutputStream`.
```clojure
(require '[babashka.fs :as fs]
'[ol.vips :as v]
'[ol.vips.operations :as ops])
(import '[java.io FileOutputStream])
(let [source-bytes (fs/read-all-bytes "dev/rabbit.jpg")]
(with-open [image (v/from-buffer source-bytes)
thumbnail (ops/thumbnail-image image 200)
out (FileOutputStream. "rabbit-thumb.png")]
(v/write-to-stream thumbnail out ".png")
(v/metadata thumbnail)))
```
## Autorotate And Inspect Outputs
`autorot` returns a closeable result map. You can inspect `:angle` and
`:flip`, and still use the same value anywhere an image is expected.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
autorot (ops/autorot image)]
{:angle (:angle autorot)
:flip (:flip autorot)
:meta (v/metadata autorot)})
;; => {:angle :d0, :flip false, :meta {:width 2490, :height 3084, :bands 3, :has-alpha? false}}
```
## Resize And Crop
Use these geometry operations when you want to rescale an image first
and then work on a precise rectangular region.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
resized (ops/resize image 0.5)
cropped (ops/extract-area resized 100 100 500 500)]
(v/write-to-file resized "rabbit-resized.jpg")
(v/write-to-file cropped "rabbit-cropped.jpg")
{:resized (v/metadata resized)
:cropped (v/metadata cropped)})
```
## Smart Thumbnailing
`ops/thumbnail` exposes libvips’s higher-level thumbnail pipeline. Enum
options such as `:size` and `:crop` are documented in `ol.vips.enums`.
```clojure
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300
{:height 300
:size :down
:crop :attention})]
(v/write-to-file thumb "rabbit-smart-thumb.jpg")
(v/metadata thumb))
```
## Transforming Images
These operations are a good fit for orientation, color interpretation,
and other structural changes you want to compose into one pipeline.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
rotated (ops/rotate image 90.0)
flipped (ops/flip rotated :horizontal)
bw (ops/colourspace flipped :b-w)]
(v/write-to-file bw "rabbit-bw.jpg"))
```
## Filters And Effects
libvips includes a large set of convolution and enhancement operations,
so common blur and sharpen passes can stay inside the same image
pipeline.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
blurred (ops/gaussblur image 3.0)
sharp (ops/sharpen image {:sigma 1.0})]
(v/write-to-file blurred "rabbit-blur.jpg")
(v/write-to-file sharp "rabbit-sharp.jpg")
{:blurred (v/metadata blurred)
:sharp (v/metadata sharp)})
```
## Composing Images
Use the composition helpers when you need to join images, build contact
sheets, or align multiple sources into one output image.
```clojure
(with-open [left (v/from-file "dev/rabbit.jpg")
right (v/from-file "dev/rabbit.jpg")
joined (ops/join left right :horizontal)
grid (ops/arrayjoin [left right left right]
{:across 2
:shim 10
:halign :centre
:valign :centre})]
(v/write-to-file joined "rabbit-joined.jpg")
(v/write-to-file grid "rabbit-grid.jpg"))
```
## Web Optimization
The save helpers accept format-specific encoder options, which makes it
easy to produce smaller web-friendly JPEG and WebP outputs from the same
source image.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")]
(v/write-to-file image "rabbit-progressive.jpg"
{:interlace true
:strip true
:Q 85})
(v/write-to-file image "rabbit.webp"
{:Q 80
:effort 4}))
```
## Calling Raw Operations
Use `v/call` when you want access to the full libvips operation surface
before a dedicated convenience wrapper exists.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
rotated (v/call "rotate" {:in image :angle 90.0})]
(v/metadata rotated))
```
Use `v/operations` to list available libvips operations and
`v/operation-info` to inspect their inputs and outputs.
## Text Overlay
Generated operation wrappers also cover image synthesis and compositing,
so overlays like text labels can stay inside libvips rather than being
rendered in a separate tool.
```clojure
(with-open [image (v/from-file "dev/rabbit.jpg")
label (ops/text "ol.vips"
{:font "Sans Bold 48"
:rgba true})
poster (ops/composite2 image label :over {:x 40 :y 40})]
(v/write-to-file poster "rabbit-poster.png")
(v/metadata poster))
```
## Going to Production
# Going to Production
This page adapts the upstream libvips production checklist to `ol.vips`.
The original reference is the libvips
[Checklist for programmers using libvips](https://www.libvips.org/API/8.18/developer-checklist.html).
Use this page as guidance for application design and deployment. It is not a
hard rulebook, but these defaults are a good starting point for long-running
services, upload pipelines, and image proxy workloads.
## Prefer smart thumbnailing over load then resize
If you are starting from a filename and want a thumbnail, prefer
[`ops/thumbnail`](api/ol-vips-operations.adoc#thumbnail) over loading the
full image and then calling
[`ops/resize`](api/ol-vips-operations.adoc#resize).
Unlike a separate load followed by resize, `ops/thumbnail` combines loading
and shrinking into one step. That lets libvips use format-specific tricks such
as shrink-on-load, which can significantly reduce memory use and latency.
This is not only about speed. Thumbnailing at load time can also improve
quality, because libvips can premultiply automatically where needed and can
render vector inputs at the target size instead of rasterizing them large and
shrinking afterwards.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [thumb (ops/thumbnail "input.jpg" 300 {:height 300})]
(v/write-to-file thumb "thumb.jpg"))
```
If you already have an image handle in hand, there is still a lower-level
[`ops/thumbnail-image`](api/ol-vips-operations.adoc#thumbnail-image)
wrapper. Treat it as an escape hatch. It wraps libvips `thumbnail_image`, so
it does not get the load-time rendering and shrink-on-load advantages that
`ops/thumbnail` gets from starting at the source.
## Use sequential access when your pipeline can stream
When you know you will read the source once in loader order, pass
`{:access :sequential}` to your loader. This often reduces memory use and can
improve throughput.
For background on why this matters, see the upstream libvips
https://www.libvips.org/API/8.18/how-it-opens-files.html["How it opens files"
chapter].
```clojure
(with-open [image (v/from-file "input.jpg" {:access :sequential})]
(v/metadata image))
```
The same applies to [`v/from-stream`](api/ol-vips.adoc#from-stream),
[`v/from-buffer`](api/ol-vips.adoc#from-buffer), and format-specific
loader operations such as
[`ops/jpegload`](api/ol-vips-operations.adoc#jpegload) and
[`ops/pngload`](api/ol-vips-operations.adoc#pngload).
## Prefer longer pipelines to many materialized steps
libvips is demand-driven and uses partial images as intermediates. In
`ol.vips`, that means you can usually build one longer pipeline of operations
without materializing every step in memory or on disk. The work is deferred
until a sink asks for pixels, such as
[`v/write-to-file`](api/ol-vips.adoc#write-to-file),
[`v/write-to-buffer`](api/ol-vips.adoc#write-to-buffer), or
[`v/write-to-stream`](api/ol-vips.adoc#write-to-stream).
This is one of the main reasons to lean into the libvips model instead of
breaking processing into many disconnected phases. Long pipelines usually stay
memory-efficient because intermediate results are represented as graph nodes
rather than fully realized images.
libvips is also horizontally threaded: threads tend to run along the pipeline
you are evaluating, rather than up and down the image. In practice, that means
longer pipelines often parallelize better than shorter ones.
```clojure
(with-open [image (v/from-file "input.jpg")
result (-> image
(ops/resize 0.5)
(ops/colourspace :b-w)
(ops/sharpen))]
(v/write-to-file result "output.jpg"))
```
If you can, aim for one coherent processing pipeline per output rather than a
series of intermediate writes, reloads, and separate mini-pipelines.
This generally works better than repeatedly writing intermediates to disk or
breaking the flow into many disconnected steps.
## Reuse shared intermediates intentionally
If one derived image is reused several times in the same request, bind it once
and pass that handle to downstream operations instead of recalculating it.
```clojure
(with-open [image (v/from-file "input.jpg")
base (ops/resize image 0.5)
preview (ops/colourspace base :srgb)
mask (ops/extract-band base 0)]
...)
```
If you need to materialize a reused intermediate so libvips does not
recalculate it, use [`v/copy-memory`](api/ol-vips.adoc#copy-memory). This
forces the current pipeline to render into a private in-memory image and
returns another image handle you can fan out to several downstream operations
or keep in an application cache for later reuse.
This can trade CPU time for higher memory use, so it is best reserved for
intermediates you know are reused often enough to justify the retained pixels.
## Put large resizes early in the pipeline
If a pipeline includes a large resize, do it near the start. After that, apply
area operations such as sharpening, then point operations.
This reduces the amount of pixel data that later stages need to touch.
## Restrict loaders and untrusted inputs
`ol.vips` starts from a secure default: libvips operations marked as
untrusted are blocked during initialization. If you handle untrusted data,
keep
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-)
set to `true` unless you have a specific trusted-input path that needs those
loaders.
If you need direct control over that default, use
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-).
Passing `true` restores the default secure posture, and passing `false`
allows operations libvips has marked as untrusted.
libvips also has a lower-level class-hierarchy blocker, exposed as
[`v/set-operation-block!`](api/ol-vips.adoc#set-operation-block-BANG-).
This lets you block broad families of operations and selectively re-enable the
subset you trust. For example, to allow only JPEG loaders:
```clojure
(require '[ol.vips :as v])
(v/set-block-untrusted-operations! true)
(v/set-operation-block! "VipsForeignLoad" true)
(v/set-operation-block! "VipsForeignLoadJpeg" false)
```
After those calls, libvips will only load JPEGs from the foreign-loader
hierarchy. This is useful when you need tighter runtime control without
building a custom libvips binary.
If you need tighter control over which loaders are present at all, prefer
shipping a custom libvips build and loading it with `-Dol.vips.native.preload`
instead of relying on a broader system installation.
See also: [Security Policy](security.adoc).
## Sanity-check images before expensive processing
Open the image, inspect cheap metadata first, and reject inputs that are too
large or unsuitable for your main pipeline.
```clojure
(with-open [image (v/from-file "upload.jpg" {:access :sequential})]
(let [width (v/width image)
height (v/height image)
interlaced (v/field image "interlaced" false)]
(when (> (* width height) 100000000)
(throw (ex-info "image too large" {:width width :height height})))
(when interlaced
(throw (ex-info "progressive images are not allowed" {})))))
```
This is especially useful for defending against decompression bombs and for
keeping progressive or interlaced inputs away from latency-sensitive paths.
In practice that usually means checking
[`v/width`](api/ol-vips.adoc#width),
[`v/height`](api/ol-vips.adoc#height), and
[`v/field`](api/ol-vips.adoc#field) before you start expensive work.
## Tune the Linux allocator for long-running glibc services
On glibc-based Linux systems, long-running multithreaded image workloads can
benefit from an alternative allocator such as
[jemalloc](https://github.com/jemalloc/jemalloc). The default allocator often
performs poorly for long-running, multithreaded processes with frequent small
allocations, and switching allocators can reduce the off-heap footprint of the
JVM when using libvips.
This is an operating environment concern rather than an `ol.vips` API setting.
On Linux that usually means configuring `LD_PRELOAD` before launching the JVM,
not changing anything inside your image pipeline.
The jemalloc project is also in a somewhat unsettled state. See
[this postmortem](https://jasone.github.io/2025/06/12/jemalloc-postmortem/) for
background, and [Facebook’s fork](https://github.com/facebook/jemalloc) for one
possible continuation point.
Musl-based Linux systems and non-Linux runtimes are generally less affected by
this specific issue.
## Disable or tune the libvips operation cache for proxy workloads
For image proxy workloads that process many unrelated images, the libvips
operation cache is often not useful. Disable it entirely with
[`v/disable-operation-cache!`](api/ol-vips.adoc#disable-operation-cache-BANG-):
```clojure
(require '[ol.vips :as v])
(v/init!)
(v/disable-operation-cache!)
```
The cache is more useful when the same or very similar operation graphs are
reused repeatedly in one process, for example a service that applies a small
set of common transforms over and over. In that kind of workload, the defaults
may be fine and you may not want to touch them.
When you do need to tune it, think about the three limits separately:
* `v/set-operation-cache-max!` limits how many recent operations libvips keeps.
Lower this when cache churn is high and you are not seeing reuse. Raise it
only if you have evidence that repeated operation graphs are being evicted too
aggressively.
* `v/set-operation-cache-max-mem!` limits how much libvips-tracked memory can
accumulate before cached operations start getting dropped. Lower this when
you want cache eviction to happen earlier under memory pressure.
* `v/set-operation-cache-max-files!` limits how many libvips-tracked file
descriptors can accumulate before cached operations start getting dropped.
Lower this when file handle pressure matters more than cache hit rate.
Tune the cache limits directly like this:
```clojure
(v/set-operation-cache-max! 0)
(v/set-operation-cache-max-mem! (* 32 1024 1024))
(v/set-operation-cache-max-files! 32)
(v/operation-cache-settings)
;; => {:max 0, :size 0, :max-mem 33554432, :max-files 32}
(v/tracked-resources)
;; => {:mem 0, :mem-highwater 0, :allocs 0, :files 0}
```
[`v/operation-cache-settings`](api/ol-vips.adoc#operation-cache-settings)
reports the current libvips cache limits and the current cache size.
[`v/tracked-resources`](api/ol-vips.adoc#tracked-resources) reports
libvips tracked memory, highwater memory, allocation count, and tracked file
count.
Those tracked counters are useful for observing trends, but they only include
resources libvips tracks itself. Memory or file descriptors used inside
external libraries may not be reflected there, so treat them as a lower bound
rather than a complete process-wide accounting.
## Ship only the native bundle you need
This is an `ol.vips`-specific deployment concern. The main library is small,
but the companion native jars contain the bundled libvips binaries and their
support libraries for a specific platform. Those jars are usually the largest
part of an `ol.vips` deployment artifact.
If you know the production target in advance, prefer packaging only the one
native companion jar that matches that runtime platform. For example, a Linux
x86-64 glibc deployment usually only needs
`com.outskirtslabs/vips-native-linux-x86-64-gnu` on the classpath.
Including several platform jars is useful for development, shared tooling, or
distributing a generic application bundle. But for a single-platform deploy it
usually just makes the image, container layer, or classpath larger without
adding runtime value, since `ol.vips` will only load the bundle that matches
the detected target platform.
If possible, build separate deployment artifacts per target platform and keep
each one trimmed to the matching native dependency set.
## ol.vips
# ol.vips
> Clojure bindings for [libvips](https://github.com/libvips/libvips) image
> processing library



`ol.vips` is a Clojure library for loading, transforming, and saving
images with [libvips](https://github.com/libvips/libvips). Use it to
build thumbnails and responsive image variants, convert formats, crop
and resize uploads, compose images, read and write metadata, and run
image pipelines from files, byte arrays, or streams.
If you have never used libvips before: it is a native image processing
library designed for fast, memory-efficient image work. `ol.vips` gives
Clojure programs access to that engine with a Clojure-first API,
generated wrappers for the full libvips operation surface, and
platform-native jars so you can use libvips from the JVM without
building the native integration yourself.
`ol.vips` uses [coffi](https://github.com/IGJoshua/coffi) and requires Java
22{plus} for FFI/FFM (Project Panama).
Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**.
It is used in production, but needs more exercise and feedback from the community before I commit to a stable version.
## Installation
Install the main library plus exactly one native jar that matches your
target platform.
```clojure
;; deps.edn
{:deps {com.outskirtslabs/vips {:mvn/version "0.0.1"}
;; change the following based on your runtime platform
com.outskirtslabs/vips-native-linux-x86-64-gnu {:mvn/version "1.3.2-1"}}}
```
https://clojars.org/search?q=group-id%3Acom.outskirtslabs+AND+artifact-id%3Avips-native*[Choose
one or more native artifacts] (only the appropriate one for the runtime
platform will be used):
* `com.outskirtslabs/vips-native-linux-x86-64-gnu`
* `com.outskirtslabs/vips-native-linux-x86-64-musl`
* `com.outskirtslabs/vips-native-linux-aarch64-gnu`
* `com.outskirtslabs/vips-native-linux-aarch64-musl`
* `com.outskirtslabs/vips-native-macos-x86-64`
* `com.outskirtslabs/vips-native-macos-aarch64`
* `com.outskirtslabs/vips-native-win32-x86-64`
The native companion jars bundle libvips 8.18.3 from `sharp-libvips` 1.3.2.
The test suite runs against that bundle.
`ol.vips` requires Java 22{plus} and native access enabled for the JVM
process:
```clojure
;; deps.edn
{:aliases
{:dev {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
```
To use your own libvips build, see
[Loading the native library](https://docs.outskirtslabs.com/ol.vips/next/introduction#loading_the_native_library).
## Quick Start
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(v/init!)
(with-open [thumb (ops/thumbnail "dev/rabbit.jpg" 300 {:auto-rotate true})
rotated (ops/rotate thumb 90.0)]
(v/write-to-file rotated "thumbnail.jpg")
(v/metadata rotated))
;; => {:width 300, :height 242, :bands 3, :has-alpha? false}
```
The top-level `ol.vips` namespace provides the image loading, saving,
metadata, and convenience helpers. `ol.vips.operations` contains
generated wrappers for libvips operations.
## Documentation
* [Docs Home](https://docs.outskirtslabs.com/ol.vips/next/)
* [Introduction](https://docs.outskirtslabs.com/ol.vips/next/introduction/)
* [Examples](https://docs.outskirtslabs.com/ol.vips/next/examples/)
* [Going to Production](https://docs.outskirtslabs.com/ol.vips/next/going-to-production/)
* [Multipage and Animated Images](https://docs.outskirtslabs.com/ol.vips/next/multipage-and-animated-images/)
* [API Reference](https://docs.outskirtslabs.com/ol.vips/next/api/ol-vips)
* [Changelog](https://docs.outskirtslabs.com/ol.vips/next/changelog/)
* [Security Policy](https://docs.outskirtslabs.com/ol.vips/next/security/)
* [Support via GitHub Issues](https://github.com/outskirtslabs/vips/issues)
## Licensing
The Clojure library `ol.vips` is copyright (C) 2026 Casey Link and is
licensed under [EUPL-1.2](https://spdx.org/licenses/EUPL-1.2.html).
The platform-native companion jars under `native/` redistribute upstream
[sharp-libvips](https://github.com/lovell/sharp-libvips/releases) binary
bundles. Those redistributed native binaries are licensed separately
from the `ol.vips` source, principally under LGPL-3.0-or-later.
## Introduction
# Introduction
`ol.vips` wraps libvips’s image graph in
[Closeable](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Closeable.html)
Clojure values. Loader functions such as `v/from-file`, `v/from-buffer`,
and `v/from-stream` return image handles, and operations such as
`ops/resize`, `ops/colourspace`, and `v/call` return new handles rather
than mutating the original image.
This model is immutable and pipeline-oriented. In the common case,
libvips reads only enough of the input to discover image metadata and
build an operation graph. Calling loaders and transforms usually does
not decode the whole image up front.
libvips is demand-driven, so the pixel work happens when something
downstream asks for image data. In `ol.vips`, that usually means a sink
such as `v/write-to-file`, `v/write-to-buffer`, or `v/write-to-stream`.
At that point libvips pulls pixels through the pipeline, processing
small regions with a horizontally threaded execution model instead of
materializing every intermediate image in memory.
Some operations that need actual pixel values can trigger evaluation
earlier, but metadata queries such as `v/width`, `v/height`, and
`v/metadata` can often be satisfied from loader metadata alone. Because
the image graph is immutable and operations are generally side-effect
free, libvips can also reuse and cache operation results internally.
`ol.vips` adds a small amount of Clojure-specific structure on top of
that model. The `ol.vips.operations` namespace is generated from libvips
introspection data, `ol.vips.enums` exposes the enum keywords used by
many options, and the top-level `ol.vips` namespace provides the file,
buffer, stream, metadata, and convenience helpers you use most often.
## Why libvips?
The typical libvips workflow is exactly what many applications need:
load a large source image, resize or transform it, and write a smaller
web-friendly JPEG, PNG, WebP, AVIF, or TIFF result. `ol.vips` gives
Clojure access to that model without shelling out to external image
tools or spawning child processes.
libvips is
https://github.com/libvips/libvips/wiki/Why-is-libvips-quick[blazing
fast] and
https://github.com/libvips/libvips/wiki/Speed-and-memory-use[memory
efficient] because it usually works on small regions of uncompressed
image data at a time, takes advantage of multiple CPU cores, and avoids
materializing every intermediate result in memory. That execution model
is a strong fit for image pipelines, thumbnails, upload processing,
preprocessing for vision workloads, and other server-side image tasks.
It also brings broad format and operation support:
[300 operations](https://www.libvips.org/API/current/func-list.html)
covering arithmetic, histograms, convolution, morphology, colour,
resampling, statistics, and more, with correct handling of colour
spaces, embedded ICC profiles, and alpha channels.
[libvips](https://github.com/libvips/libvips) was originally created at
Birkbeck College and is currently maintained by
[John Cupitt](https://github.com/jcupitt) and other contributors.
### Bindings Generation
libvips itself is very amenable to automatic bindings generation. The
core of `ol.vips` is small introspection layer that loads the native
library then introspects it to generate `ol.vips.operations` and
`ol.vips.enums`, and then a small runtime for loading/reading/writing.
As a convenience for Clojure users there are separate jars available
with the native libraries per-platform, but `ol.vips` it self does not
depend on them directly. You can depend on one (or more) of those native
deps, or provide your own libvips build with `-Dol.vips.native.preload`.
You’ll need to choose one.
The published native companion jars bundle libvips 8.18.3 from
`sharp-libvips` 1.3.2. The test suite runs against that bundle.
This means that you can even use a custom libvips build or a newer
version of libvips without having to wait for `ol.vips` to catch up
(though I expect to keep it up to date). Any new or updated operations
in libvips can be used with this library using the lower level
`ol.vips/call` function. You can even generate the bindings yourself if
you need to, simply use `ol.vips.codegen`.
## Going to Production
See [Going to Production](https://docs.outskirtslabs.com/ol.vips/next/going-to-production)
for production guidance on pipeline shape, sequential access, untrusted
inputs, sanity checks, allocator tuning, and cache settings in `ol.vips`.
See [Examples](examples.adoc)
for runnable snippets that cover file, buffer, stream, metadata,
thumbnailing, transforms, composition, and animated-image workflows.
## Loading the native library
`ol.vips` uses this procedure to load the native components:
1. Load any explicit libraries listed in `-Dol.vips.native.preload`.
2. Load the extracted libraries from the platform native jar on the
classpath.
3. If that fails, fall back to the system library loader, which can
resolve `libvips` from `LD_LIBRARY_PATH`.
### 1. Preload
`-Dol.vips.native.preload` is for exact native library file paths. Use
it when you want to point `ol.vips` at a specific custom build in a
non-standard location, or when you need to preload dependency libraries
before libvips itself. On Nix-like systems that can include things like
`libstdc{plus}{plus}.so.6` as well as your `libvips` library. The value
is a single string containing one or more full library file paths
separated by the OS path separator, which is `:` on Linux and macOS and
`;` on Windows.
If `ol.vips.native.preload` is set, those entries are always loaded
first. If any preload entry fails to load, the packaged native-jar path
is abandoned for that initialization attempt and `ol.vips` falls back to
the system library loader instead of continuing with the native jar.
### 2. Classpath native bundle
`ol.vips` detects the current platform, builds a resource path like
`ol/vips/native/-` on macOS and Windows or
`ol/vips/native/--` on Linux, and then looks
for `manifest.edn` under that path.
For example, Linux x86-64 glibc resolves to
`ol/vips/native/linux-x86-64-gnu/manifest.edn`. If that resource is
present, `ol.vips` reads the manifest, extracts the bundled native
libraries into the local cache, and loads those extracted files.
You can override parts of that platform detection with
`-Dol.vips.native.platform-id`, `-Dol.vips.native.os`,
`-Dol.vips.native.arch`, and `-Dol.vips.native.libc`.
In practice, this means the companion jar for the current runtime
platform needs to be on the classpath, meaning the current OS, CPU
architecture, and Linux libc when applicable. This path is attempted
after any explicit `ol.vips.native.preload` entries.
In the successful packaged case, `v/init!` will report
`:native-load-source :packaged` and expose the extracted primary library
path in `:primary-library-path`, which is useful when debugging exactly
what got loaded.
### 3. System fallback
The system-library fallback loads by library name rather than full file
path. Use this when you want the OS or JVM loader to resolve libvips
from `LD_LIBRARY_PATH`, standard system locations, or other
platform-specific loader configuration. A platform native jar is not
required for this path; if the packaged load fails, including because no
matching native jar is present on the classpath, `ol.vips` can still
fall back to the system loader.
By default the system fallback tries `vips-cpp` and then `vips`. Most
users should not need to change this. `-Dol.vips.native.system-libs`
exists for the uncommon case where your environment exposes libvips
under different system library names. Like `ol.vips.native.preload`, it
accepts multiple entries separated by the OS path separator.
The fallback only applies to native library loading. If the packaged
libraries load successfully but `vips_init` fails afterwards,
`ol.vips` does not automatically retry via the system loader. For
debugging, `v/init!` exposes runtime state including whether
initialization came from the packaged path or the system fallback.
## Multipage and Animated Images
# Multipage and Animated Images
This page adapts the upstream libvips guide for `ol.vips`. The original
reference is
[Multipage and animated images](https://www.libvips.org/API/8.18/multipage-and-animated-images.html).
libvips represents animated and multipage images as one tall image plus
metadata describing how that strip should be interpreted. In `ol.vips`, the
main metadata fields are
[`v/pages`](api/ol-vips.adoc#pages),
[`v/page-height`](api/ol-vips.adoc#page-height),
[`v/page-delays`](api/ol-vips.adoc#page-delays), and
[`v/loop-count`](api/ol-vips.adoc#loop-count).
This model works best when every page or frame has the same dimensions. That
is common for animated GIF and WebP, and also for some TIFF or PDF inputs. If
page sizes vary, read and process them one at a time instead of treating the
file as one uniform strip.
## Reading multipage images
By default, libvips usually reads only the first page or frame. Pass `{:n -1}`
to load every page, or use `{:page ... :n ...}` to load a single page or a
range.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})]
(select-keys (v/metadata image)
[:width :height :pages :page-height :loop :delay]))
;; => {:width 85
;; :height 385
;; :pages 5
;; :page-height 77
;; :loop 32761
;; :delay [0 50 50 50 50]}
```
Points to note:
* [`ops/gifload`](api/ol-vips-operations.adoc#gifload) with `{:n -1}` loads
every frame in the animation.
* `:page-height` is the height of one frame within the strip.
* `:pages * :page-height == :height` for uniform-height images.
* `:loop` is the loop count for animated formats.
* `:delay` is a vector of frame delays in milliseconds.
You can also load a subset of frames:
```clojure
(with-open [two-frames (ops/gifload "test/fixtures/cogs.gif" {:page 2 :n 2})]
(select-keys (v/metadata two-frames) [:height :pages :page-height]))
```
Be careful interpreting metadata on partial loads. The loaded strip height
reflects the selected range, but some loaders can still preserve source
metadata such as the original total `n-pages`. For frame slicing logic, rely on
the loaded image dimensions together with `page-height`, not only on the
reported source page count.
The same loader options apply to other multipage-capable loaders such as
[`ops/webpload`](api/ol-vips-operations.adoc#webpload) and
[`ops/tiffload`](api/ol-vips-operations.adoc#tiffload), or to the generic
[`v/from-file`](api/ol-vips.adoc#from-file) path when you want format
autodetection.
For PDF input, remember that `ol.vips` blocks libvips operations marked as
untrusted during initialization. If you have a trusted PDF input path and need
to load it, opt in first with
[`v/set-block-untrusted-operations!`](api/ol-vips.adoc#set-block-untrusted-operations-BANG-)
set to `false`.
## Writing multipage images
If an image already has the right strip layout and metadata, writing an
animated or multipage image is just a normal save. For example, you can load a
GIF animation and write it back as animated WebP:
```clojure
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})]
(v/write-to-file image "cogs.webp"))
```
That works because the loaded image already carries the `n-pages`,
`page-height`, `loop`, and `delay` metadata that the saver needs.
The same pattern applies to multipage TIFF output with
[`ops/tiffsave`](api/ol-vips-operations.adoc#tiffsave) or
to generic saves through [`v/write-to-file`](api/ol-vips.adoc#write-to-file)
when libvips can infer the target format from the filename.
## Building an animation from frames
The upstream libvips examples build a vertical strip and then set metadata by
hand. In `ol.vips`, prefer
[`v/assemble-pages`](api/ol-vips.adoc#assemble-pages) when you already have
a sequence of equal-sized frames. It joins the frames into one strip and sets
the page metadata for you.
```clojure
(require '[ol.vips :as v]
'[ol.vips.operations :as ops])
(with-open [base (v/from-file "test/fixtures/puppies.jpg")
frame-a (ops/extract-area base 0 0 40 30)
frame-b (ops/extract-area base 10 10 40 30)
frame-c (ops/extract-area base 20 20 40 30)
animated (v/assemble-pages [frame-a frame-b frame-c]
{:loop 2
:delay [80 120 160]})]
(v/write-to-file animated "puppies.gif")
(select-keys (v/metadata animated)
[:width :height :pages :page-height :loop :delay]))
```
Here, `frame-a`, `frame-b`, and `frame-c` are just ordinary image handles.
`v/assemble-pages` does not require a special frame type. The `frames`
argument is simply a non-empty collection of same-sized images, with each image
becoming one animation frame in the output.
In practice, those images can come from any image-producing operation, not only
from [`ops/extract-area`](api/ol-vips-operations.adoc#extract-area). You
can resize, crop, composite, render text, or load separate files first, then
pass the resulting image handles to `v/assemble-pages`.
If you already have a tall strip and need to annotate or adjust it, use
[`v/assoc-pages`](api/ol-vips.adoc#assoc-pages),
[`v/assoc-page-height`](api/ol-vips.adoc#assoc-page-height),
[`v/assoc-page-delays`](api/ol-vips.adoc#assoc-page-delays), and
[`v/assoc-loop-count`](api/ol-vips.adoc#assoc-loop-count).
## Applying operations page by page
For uniform-height multipage images, the page-aware helpers apply ordinary
operations frame by frame and keep the metadata consistent.
```clojure
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1})
cropped (v/extract-area-pages image 10 7 50 50)
turned (v/rot-pages cropped :d90)
looped (v/assoc-loop-count turned 2)]
(v/write-to-file looped "cogs-turned.gif")
(select-keys (v/metadata looped)
[:width :height :pages :page-height :loop :delay]))
```
The main helpers are:
* [`v/extract-area-pages`](api/ol-vips.adoc#extract-area-pages)
* [`v/embed-pages`](api/ol-vips.adoc#embed-pages)
* [`v/rot-pages`](api/ol-vips.adoc#rot-pages)
* [`v/assemble-pages`](api/ol-vips.adoc#assemble-pages)
For single-page inputs, these helpers fall back to the ordinary single-image
behavior. For true multipage work, they assume that the image can be divided
cleanly into equal-height pages.
## Security
# Security
Please report vulnerabilities through [GitHub Security Advisories](https://github.com/outskirtslabs/vips/security/advisories).
For general policy and support expectations, see [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).
## Open Source Vital Signs
# Open Source Vital Signs
This page outlines the taxonomy I employ to describe the maturity levels of my
various open source projects. By understanding a project’s current status, you
can make informed decisions about its suitability for your needs, the level of
engagement you should expect from me, and whether it’s on track for long-term
support.
If you rely on a project for critical use cases, prefer those marked **Stable**.
If you enjoy shaping software early, look for **Experimental** and **Maturing** projects.
## Experimental
_Beware of moving parts and sharp edges._
Experimental projects are early-stage explorations. I am validating ideas, APIs, and architecture. Breaking changes are likely. Documentation may be incomplete. The goal is learning and iteration, not stability.
Contribute early and often. Experimental projects are where your input can make the biggest difference.
| | |
| --- | --- |
| Activity | Supported |
| New features | Yes |
| Feature requests | Yes |
| Bug and security fixes | No |
| Bug reports | Yes |
| New releases | Maybe? |
## Maturing
_Actively developed and moving toward stability._
Maturing projects have graduated from my mad science lab and taken up residence
in the "maybe someday this could be useful" wing of my brain. They’ve proven
themselves useful, are under active development, and are moving closer to
stability. APIs may change, but those changes are more deliberate now.
Documentation is improving, and I try not to break things quite as much.
Backward compatibility is considered, though it can’t be guaranteed.
If you’re cool with the idea of keeping up with the project’s evolution (and
occasional minor breaking changes), maturing projects are worth a closer look.
They might even be suitable for early production use.
| | |
| --- | --- |
| Activity | Supported |
| New features | Limited |
| Feature requests | Limited |
| Bug and security fixes | Yes |
| Bug reports | Yes |
| New releases | Yes |
## Stable
_Production-ready and well behaved._
Stable projects have left their rebellious teenager phase behind. They’ve found steady jobs, bought houses, and aren’t looking for new hobbies.
The scope is defined, the API is predictable, and documentation is complete enough that you should be able to use them independently.
New features are rare, not because these projects are neglected, but because they do what they set out to do.
If they had driver’s licenses, I’d feel comfortable letting you loan them out for road trips.
Use Stable projects in production with confidence. Major changes will be rare and clearly communicated.
| | |
| --- | --- |
| Activity | Supported |
| New features | Limited |
| Feature requests | No |
| Bug and security fixes | Yes |
| Bug reports | Yes |
| New releases | Yes |
## Retired
_Inactive and out of service._
Retired projects have left the building for good. They may depend on outdated technologies, have been replaced by newer projects, or no longer align with my interests.
If you’re inclined, feel free to fork them and keep the dream alive; but I do not plan to review issues, accept pull requests, or publish new releases.
If you’re inclined, feel free to keep the dream alive on your own terms (just keep the license intact).
| | |
| --- | --- |
| Activity | Supported |
| New features | No |
| Feature requests | No |
| Bug and security fixes | No |
| Bug reports | No |
| New releases | No |
## Static
_Code with a "do not feed" sign._
Static projects are like exhibits at the zoo: they’re here to be observed and appreciated, not taken home.
These are snippets of code published alongside research papers or blog posts, demonstration examples, or educational materials.
Don’t expect any updates; I won’t be making any, because the purpose of these projects is to serve as a reference, not to build your production system.
## Recent Releases
# Recent Releases
Full release feed across all tracked projects.
| Date | Library | Version |
| --- | --- | --- |
| 2026-07-13 | [fluent-tooling](../fluent-tooling/0.0.1/) | `v0.0.1` |
| 2026-07-13 | [ol.vips](../ol.vips/0.0.1/) | `v0.0.1` |
| 2026-04-29 | [datomic-pro-flake](../datomic-pro-flake/0.14/) | `v0.14.0` |
| 2026-04-01 | [datomic-pro-flake](../datomic-pro-flake/0.13/) | `v0.13.0` |
| 2026-03-12 | [ol.dirs](../ol.dirs/0.1/) | `v0.1.0` |
| 2026-03-12 | [ol.sops](../ol.sops/0.1/) | `v0.1.0` |
| 2026-03-09 | [ol.trixnity](../ol.trixnity/next/) | `experimental` |
| 2026-02-09 | [ol.llx](../ol.llx/next/) | `experimental` |
| 2026-02-03 | [datomic-pro-flake](../datomic-pro-flake/0.12/) | `v0.12.0` |
| 2026-02-03 | [datomic-pro-flake](../datomic-pro-flake/0.11/) | `v0.11.0` |
| 2026-02-03 | [datomic-pro-flake](../datomic-pro-flake/0.10/) | `v0.10.0` |
| 2025-12-18 | [nixos-hetzner](../nixos-hetzner/next/) | `static` |
| 2025-12-18 | [nixos-hetzner-demo](../nixos-hetzner-demo/next/) | `static` |
| 2025-12-14 | [datomic-pro-flake](../datomic-pro-flake/0.9/) | `v0.9.0` |
| 2025-10-28 | [ol.clave](../ol.clave/next/) | `experimental` |
| 2025-10-20 | [h2o-zig](../h2o-zig/next/) | `maturing` |
| 2025-09-08 | [datomic-pro-flake](../datomic-pro-flake/0.8/) | `v0.8.0` |
| 2025-09-08 | [ol.sfv](../ol.sfv/0.1/) | `v0.1.0` |
| 2025-08-18 | [ol.client-ip](../ol.client-ip/0.1/) | `v0.1.0` |
| 2025-04-22 | [datahike-sqlite](../datahike-sqlite/next/) | `experimental` |
| 2025-04-14 | [datastar-expressions](../datastar-expressions/next/) | `experimental` |
## Security Policy
# Security Policy
## Report a Security Issue
I take security reports seriously and will give them prompt attention.
Security issues should be reported privately to `casey@outskirtslabs.com`, please use the string `OSS-SEC` somewhere in the subject line.
You may encrypt emails with [my public PGP key](https://casey.link/pgp.asc).
_Please_ do not file public issues for security vulnerabilities.
## Security Advisories
Remediation of security vulnerabilities for [maturing](open-source-vital-signs.adoc#maturing) and [stable](open-source-vital-signs.adoc#stable) projects is prioritized.
I endeavor to coordinate remediation with third-party stakeholders and am committed to transparency in the disclosure process.
Security issues are announced via Release notes and Security Advisories on the affected project’s repository, as well as at [docs.outskirtslabs.com](https://docs.outskirtslabs.com) on a best-effort basis.
## Support
# Support
If these projects bring you joy or save you time or money, supporting the work helps me keep them healthy and moving forward (it also helps keep me healthy and moving forward). There are two straightforward ways to do that.
## You are human person
If you are an individual developer or hobbyist, your sponsorship helps fund ongoing maintenance and releases.
Liberapay is my preferred option, because it has lower fees, supports my family, and doesn’t enable a monstrous mega-corporation hellbent on world domination.
But I won’t judge if you use GitHub Sponsors; it works too.
Even small recurring or one-time sponsorships make a real difference, it signals to the community that there is something of value here.
## You are a legal entity
If your company depends on one or more Outskirts Labs projects, I offer paid consulting engagements.
Typical engagements include:
* architecture and implementation guidance
* integrations and custom feature/bug development
* production hardening and reliability work
* team enablement and long-term maintenance planning
To discuss scope, timeline, and budget, email mailto:casey@outskirtslabs.com[casey@outskirtslabs.com].
(p.s. All engagements come with a EU-friendly VAT invoice that will make your bookeeper happy.)