Heavy Computation's Cloudflare toolkit for Clojure.
A thin, batteries-included wrapper over the Cloudflare products we reach for in every webapp. Features are added incrementally:
This namespace is the public entry point and re-exports the high-level API as features land. For the full surface (and the raw signed-HTTP escape hatch), use the per-product namespaces directly.
get/list shadow clojure.core names and are excluded here; refer to them
qualified (e.g. cloudflare/get).
Heavy Computation's Cloudflare toolkit for Clojure. A thin, batteries-included wrapper over the Cloudflare products we reach for in every webapp. Features are added incrementally: - R2 object storage (S3-compatible) -> com.heavycomputation.cloudflare.r2 - Images delivery/upload -> com.heavycomputation.cloudflare.images This namespace is the public entry point and re-exports the high-level API as features land. For the full surface (and the raw signed-HTTP escape hatch), use the per-product namespaces directly. `get`/`list` shadow `clojure.core` names and are excluded here; refer to them qualified (e.g. `cloudflare/get`).
(bucket {:keys [account-id access-key-id secret-access-key bucket region]
:or {region "auto"}})Build an R2 bucket handle from a config map:
{:account-id "abc123" :access-key-id "..." :secret-access-key "..." :bucket "uploads" :region "auto"} ; optional, see note below
:region is the SigV4 signing region. R2 accepts only its own region codes
(auto, weur, enam, wnam, eeur, apac, oc) and rejects AWS regions
like eu-west-2 with InvalidRegionName. Because we sign requests ourselves
rather than going through an SDK's endpoint resolver, we can hand R2 its own
code directly; auto (the default) works for every bucket regardless of where
it lives.
Returns {:client <s3 client> :bucket <name>}, suitable for put and
request.
Build an R2 bucket handle from a config map:
{:account-id "abc123"
:access-key-id "..."
:secret-access-key "..."
:bucket "uploads"
:region "auto"} ; optional, see note below
`:region` is the SigV4 *signing* region. R2 accepts only its own region codes
(`auto`, `weur`, `enam`, `wnam`, `eeur`, `apac`, `oc`) and rejects AWS regions
like `eu-west-2` with `InvalidRegionName`. Because we sign requests ourselves
rather than going through an SDK's endpoint resolver, we can hand R2 its own
code directly; `auto` (the default) works for every bucket regardless of where
it lives.
Returns `{:client <s3 client> :bucket <name>}`, suitable for `put` and
`request`.(delete {:keys [client bucket]} key)Delete the object at key. Idempotent — succeeds whether or not it existed.
Returns true.
Delete the object at `key`. Idempotent — succeeds whether or not it existed. Returns `true`.
(delete-many bucket keys)Delete many objects in one (or, above 1000 keys, a few) batched requests.
Returns {:deleted [key …] :errors [{:key :code :message} …]}.
Delete many objects in one (or, above 1000 keys, a few) batched requests.
Returns `{:deleted [key …] :errors [{:key :code :message} …]}`.(exists? bucket key)Whether an object exists under key.
Whether an object exists under `key`.
(get {:keys [client bucket]} key)Fetch an object: its metadata (see head) plus :body bound to a byte[],
or nil if it doesn't exist. Buffers the whole object in memory — use
get-stream for large objects.
Fetch an object: its metadata (see `head`) plus `:body` bound to a `byte[]`, or `nil` if it doesn't exist. Buffers the whole object in memory — use `get-stream` for large objects.
(get-bytes {:keys [client bucket]} key)Fetch an object's contents as a byte[], or nil if it doesn't exist.
Reads the whole object into memory — use get-stream for large objects.
Fetch an object's contents as a `byte[]`, or `nil` if it doesn't exist. Reads the whole object into memory — use `get-stream` for large objects.
(get-stream {:keys [client bucket]} key)Fetch an object for streaming: returns its metadata (see head) plus :body
bound to an InputStream, or nil if it doesn't exist. The caller must close
:body (e.g. with with-open) — it holds the live connection.
Fetch an object for streaming: returns its metadata (see `head`) plus `:body` bound to an `InputStream`, or `nil` if it doesn't exist. The caller must close `:body` (e.g. with `with-open`) — it holds the live connection.
(head {:keys [client bucket]} key)Object metadata without its body, or nil if the object doesn't exist.
Returns {:content-type :bytes :etag :last-modified :cache-control :metadata}.
Object metadata without its body, or `nil` if the object doesn't exist.
Returns `{:content-type :bytes :etag :last-modified :cache-control
:metadata}`.(list {:keys [client bucket]}
&
[{:keys [prefix delimiter max-keys continuation-token]}])List one page of objects. Options: :prefix restrict to keys starting with this string :delimiter roll keys up to common prefixes (e.g. "/" for folders) :max-keys page size (S3 default/cap is 1000) :continuation-token fetch the page following a previous truncated result
Returns {:objects [{:key :size :etag :last-modified} …] :common-prefixes ["a/" …] :truncated? bool :continuation-token <token-or-nil>}. When :truncated? is true,
pass :continuation-token back to get the next page (or use list-seq).
List one page of objects. Options:
:prefix restrict to keys starting with this string
:delimiter roll keys up to common prefixes (e.g. "/" for folders)
:max-keys page size (S3 default/cap is 1000)
:continuation-token fetch the page following a previous truncated result
Returns `{:objects [{:key :size :etag :last-modified} …]
:common-prefixes ["a/" …]
:truncated? bool
:continuation-token <token-or-nil>}`. When `:truncated?` is true,
pass `:continuation-token` back to get the next page (or use `list-seq`).(list-seq bucket & [opts])Lazily enumerate every object matching opts (same options as list, minus
:continuation-token), transparently following pagination. Each element is an
object map {:key :size :etag :last-modified}. Pages are fetched as the seq
is consumed, so realizing it performs I/O and may throw.
Lazily enumerate every object matching `opts` (same options as `list`, minus
`:continuation-token`), transparently following pagination. Each element is an
object map `{:key :size :etag :last-modified}`. Pages are fetched as the seq
is consumed, so realizing it performs I/O and may throw.(presigned-url {:keys [client bucket]}
key
&
[{:keys [method expires] :or {method :get expires 900}}])Build a presigned URL for key that grants temporary, credential-free access
— anyone with the URL can use it until it expires. Options:
:method :get (default) for downloads, :put for direct browser uploads
:expires validity window in seconds (default 900 = 15 min; max 7 days)
For :put, a client PUTs the file straight to the returned URL, so large
uploads never pass through your server. Returns the URL string.
Build a presigned URL for `key` that grants temporary, credential-free access — anyone with the URL can use it until it expires. Options: :method :get (default) for downloads, :put for direct browser uploads :expires validity window in seconds (default 900 = 15 min; max 7 days) For `:put`, a client `PUT`s the file straight to the returned URL, so large uploads never pass through your server. Returns the URL string.
(put {:keys [client bucket]} key body & [opts])Upload body to bucket under key, choosing single-PUT or multipart
automatically.
body may be a java.io.File, byte[], or InputStream. Content-Type is
inferred from the key's extension unless overridden.
Options (all optional): :content-type override the inferred Content-Type :multipart-threshold bytes at/above which multipart kicks in (~100 MB) :part-size multipart part size in bytes (~100 MB) :concurrency number of parts uploaded at once (4) :metadata map of custom metadata (stored as x-amz-meta-*) :cache-control Cache-Control header value
Returns {:key key :bucket bucket :etag etag :bytes n}. Throws ex-info on
failure; an interrupted multipart upload is aborted first.
Upload `body` to `bucket` under `key`, choosing single-PUT or multipart
automatically.
`body` may be a `java.io.File`, `byte[]`, or `InputStream`. Content-Type is
inferred from the key's extension unless overridden.
Options (all optional):
:content-type override the inferred Content-Type
:multipart-threshold bytes at/above which multipart kicks in (~100 MB)
:part-size multipart part size in bytes (~100 MB)
:concurrency number of parts uploaded at once (4)
:metadata map of custom metadata (stored as x-amz-meta-*)
:cache-control Cache-Control header value
Returns `{:key key :bucket bucket :etag etag :bytes n}`. Throws `ex-info` on
failure; an interrupted multipart upload is aborted first.(request {:keys [client bucket]} {:keys [key] :as req})Escape hatch to the raw signed-S3 surface for ops put doesn't cover (range
gets, conditional ops, tagging, listing, ...).
req is {:method :key :query :headers :body}: :method is the HTTP verb,
:key (optional) the object key, :query/:headers maps, :body a byte[].
The path is built from the handle's bucket (and :key if given). Returns the
raw response {:status :headers :body} without throwing on 4xx/5xx, so the
caller controls error handling.
Escape hatch to the raw signed-S3 surface for ops `put` doesn't cover (range
gets, conditional ops, tagging, listing, ...).
`req` is `{:method :key :query :headers :body}`: `:method` is the HTTP verb,
`:key` (optional) the object key, `:query`/`:headers` maps, `:body` a byte[].
The path is built from the handle's bucket (and `:key` if given). Returns the
raw response `{:status :headers :body}` without throwing on 4xx/5xx, so the
caller controls error handling.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |