Client configuration for interacting with Supabase.
Creates and manages immutable client maps containing connection options, service URLs, and configuration for your Supabase project. The client is a plain Clojure map validated with Malli schemas.
(require '[supabase.core.client :as client])
;; Create a client with defaults
(client/make-client "https://abc.supabase.co" "my-api-key")
;; Create a client with options
(client/make-client "https://abc.supabase.co" "my-api-key"
:db {:schema "another"}
:auth {:flow-type "pkce"}
:global {:headers {"custom-header" "custom-value"}})
;; Update the access token for authenticated requests
(client/update-access-token client "new-token")
{:base-url "https://abc.supabase.co"
:api-key "my-api-key"
:access-token "my-api-key"
:auth-url "https://abc.supabase.co/auth/v1"
:database-url "https://abc.supabase.co/rest/v1"
:storage-url "https://abc.supabase.co/storage/v1"
:functions-url "https://abc.supabase.co/functions/v1"
:realtime-url "https://abc.supabase.co/realtime/v1"
:db {:schema "public"}
:client-info {"supabase-clj" "0.7.0"}
:global {:headers {}}
:auth {:auto-refresh-token true, :flow-type "implicit", ...}
:storage {:use-new-hostname false}}
The :client-info map is rendered into the structured x-client-info
header on every request (see format-client-info). Service modules
register themselves via with-client-info.
See https://supabase.com/docs/reference/javascript/initializing
Client configuration for interacting with Supabase.
Creates and manages immutable client maps containing connection options,
service URLs, and configuration for your Supabase project. The client is
a plain Clojure map validated with Malli schemas.
## Usage
(require '[supabase.core.client :as client])
;; Create a client with defaults
(client/make-client "https://abc.supabase.co" "my-api-key")
;; Create a client with options
(client/make-client "https://abc.supabase.co" "my-api-key"
:db {:schema "another"}
:auth {:flow-type "pkce"}
:global {:headers {"custom-header" "custom-value"}})
;; Update the access token for authenticated requests
(client/update-access-token client "new-token")
## Client Map Structure
{:base-url "https://abc.supabase.co"
:api-key "my-api-key"
:access-token "my-api-key"
:auth-url "https://abc.supabase.co/auth/v1"
:database-url "https://abc.supabase.co/rest/v1"
:storage-url "https://abc.supabase.co/storage/v1"
:functions-url "https://abc.supabase.co/functions/v1"
:realtime-url "https://abc.supabase.co/realtime/v1"
:db {:schema "public"}
:client-info {"supabase-clj" "0.7.0"}
:global {:headers {}}
:auth {:auto-refresh-token true, :flow-type "implicit", ...}
:storage {:use-new-hostname false}}
The `:client-info` map is rendered into the structured `x-client-info`
header on every request (see [[format-client-info]]). Service modules
register themselves via [[with-client-info]].
See https://supabase.com/docs/reference/javascript/initializingAnomaly-based error handling for the Supabase Clojure SDK.
Errors are represented as plain maps following the cognitect/anomalies convention. This avoids tagged tuples and exceptions by default, matching the data-driven philosophy of libraries like cognitect/aws-api.
The SDK maps HTTP status codes and domain errors to these categories:
:cognitect.anomalies/incorrect — bad request, validation failure (4xx client errors):cognitect.anomalies/forbidden — authentication/authorization failure (401, 403):cognitect.anomalies/not-found — resource not found (404):cognitect.anomalies/conflict — resource already exists (409):cognitect.anomalies/busy — rate limited, resource locked (423, 429):cognitect.anomalies/unavailable — server error, service unavailable (5xx):cognitect.anomalies/fault — unexpected server-side failureAn anomaly map always contains :cognitect.anomalies/category and may include:
:cognitect.anomalies/message — human-readable error description:supabase/service — originating service (:auth, :storage, etc.):supabase/code — semantic error code keyword (e.g. :not-found):http/status — original HTTP status code:http/body — response body (parsed or raw):http/headers — response headers(require '[supabase.core.error :as error])
;; Check if a result is an error
(error/anomaly? result)
;; Create an anomaly from an HTTP response
(error/from-http-response 404 {:message "Not Found"} :storage)
;; Create a domain-specific anomaly
(error/anomaly :cognitect.anomalies/incorrect
{:supabase/service :auth
:cognitect.anomalies/message "Invalid credentials"})
Anomaly-based error handling for the Supabase Clojure SDK.
Errors are represented as plain maps following the cognitect/anomalies convention.
This avoids tagged tuples and exceptions by default, matching the data-driven
philosophy of libraries like cognitect/aws-api.
## Anomaly Categories
The SDK maps HTTP status codes and domain errors to these categories:
- `:cognitect.anomalies/incorrect` — bad request, validation failure (4xx client errors)
- `:cognitect.anomalies/forbidden` — authentication/authorization failure (401, 403)
- `:cognitect.anomalies/not-found` — resource not found (404)
- `:cognitect.anomalies/conflict` — resource already exists (409)
- `:cognitect.anomalies/busy` — rate limited, resource locked (423, 429)
- `:cognitect.anomalies/unavailable` — server error, service unavailable (5xx)
- `:cognitect.anomalies/fault` — unexpected server-side failure
## Structure
An anomaly map always contains `:cognitect.anomalies/category` and may include:
- `:cognitect.anomalies/message` — human-readable error description
- `:supabase/service` — originating service (`:auth`, `:storage`, etc.)
- `:supabase/code` — semantic error code keyword (e.g. `:not-found`)
- `:http/status` — original HTTP status code
- `:http/body` — response body (parsed or raw)
- `:http/headers` — response headers
## Usage
(require '[supabase.core.error :as error])
;; Check if a result is an error
(error/anomaly? result)
;; Create an anomaly from an HTTP response
(error/from-http-response 404 {:message "Not Found"} :storage)
;; Create a domain-specific anomaly
(error/anomaly :cognitect.anomalies/incorrect
{:supabase/service :auth
:cognitect.anomalies/message "Invalid credentials"})Composable HTTP request builder and executor for Supabase services.
Requests are built as plain maps using a threading-friendly API, then
executed through a supabase.core.transport/Transport. The default
transport wraps Hato; tests and integrators can swap it.
A request map contains:
:method — HTTP method keyword (:get :post :put :patch :delete):url — fully resolved URL string:headers — map of header name to value:query — map of query parameter name to value:body — request body (map, string, bytes, File, InputStream, or nil):multipart — vector of multipart parts (mutually exclusive with :body):response-as — :string (default), :byte-array, :stream, :reader:decoder — fn from raw body to parsed body (default JSON for :string):error-parser — fn [status body headers service] → anomaly map:log? — emit debug/error log lines for this request:timeout — per-request timeout (ms):transport — explicit transport instance (overrides client transport):retries — retry policy override (true, integer, or opts map;
false disables). Overrides the client :retries.:on-event — telemetry fn for this request (overrides client
:on-event):service — originating service keyword (:auth, :storage, etc.):client — reference to the client map(require '[supabase.core.http :as http])
;; Build and execute a request
(-> (http/request client)
(http/with-service-url :auth-url "/signup")
(http/with-method :post)
(http/with-body {:email "user@example.com" :password "secret"})
(http/execute))
;; => {:status 200, :body {...}, :headers {...}} on success
;; => anomaly map on HTTP error (status >= 400)
;; Streaming response (no decoding)
(-> (http/request client)
(http/with-service-url :storage-url "/object/bucket/path")
(http/with-response-as :stream)
(http/execute))
;; => {:status 200, :body #object[java.io.InputStream ...], :headers {...}}
;; Multipart upload
(-> (http/request client)
(http/with-service-url :storage-url "/object/bucket/path")
(http/with-method :post)
(http/with-multipart [{:name "file" :content (io/file "a.png")
:content-type "image/png" :filename "a.png"}])
(http/execute))
Composable HTTP request builder and executor for Supabase services.
Requests are built as plain maps using a threading-friendly API, then
executed through a [[supabase.core.transport/Transport]]. The default
transport wraps Hato; tests and integrators can swap it.
## Request map
A request map contains:
- `:method` — HTTP method keyword (`:get` `:post` `:put` `:patch` `:delete`)
- `:url` — fully resolved URL string
- `:headers` — map of header name to value
- `:query` — map of query parameter name to value
- `:body` — request body (map, string, bytes, File, InputStream, or nil)
- `:multipart` — vector of multipart parts (mutually exclusive with `:body`)
- `:response-as` — `:string` (default), `:byte-array`, `:stream`, `:reader`
- `:decoder` — fn from raw body to parsed body (default JSON for `:string`)
- `:error-parser` — fn `[status body headers service]` → anomaly map
- `:log?` — emit debug/error log lines for this request
- `:timeout` — per-request timeout (ms)
- `:transport` — explicit transport instance (overrides client transport)
- `:retries` — retry policy override (`true`, integer, or opts map;
`false` disables). Overrides the client `:retries`.
- `:on-event` — telemetry fn for this request (overrides client
`:on-event`)
- `:service` — originating service keyword (`:auth`, `:storage`, etc.)
- `:client` — reference to the client map
## Usage
(require '[supabase.core.http :as http])
;; Build and execute a request
(-> (http/request client)
(http/with-service-url :auth-url "/signup")
(http/with-method :post)
(http/with-body {:email "user@example.com" :password "secret"})
(http/execute))
;; => {:status 200, :body {...}, :headers {...}} on success
;; => anomaly map on HTTP error (status >= 400)
;; Streaming response (no decoding)
(-> (http/request client)
(http/with-service-url :storage-url "/object/bucket/path")
(http/with-response-as :stream)
(http/execute))
;; => {:status 200, :body #object[java.io.InputStream ...], :headers {...}}
;; Multipart upload
(-> (http/request client)
(http/with-service-url :storage-url "/object/bucket/path")
(http/with-method :post)
(http/with-multipart [{:name "file" :content (io/file "a.png")
:content-type "image/png" :filename "a.png"}])
(http/execute))Pure retry policy for transient HTTP failures in the Supabase Clojure SDK.
This namespace decides whether and how long to wait before retrying a
failed request. It performs no sleeping and no I/O: supabase.core.http
calls these functions from its retry loop and owns the actual waiting.
A failure is worth retrying when it is likely to resolve on its own:
transient-status?)transient-exception?, which also walks the cause chain)Delays combine two sources, with the server always winning:
Retry-After response header, when present and parseable
(retry-after-ms), either as delay-seconds or an RFC 1123 HTTP-date.backoff-ms), capped at
:max-delay-ms.next-delay-ms applies that precedence. merge-opts resolves client-level
and request-level retry options against default-opts.
(require '[supabase.core.retry :as retry])
(retry/transient-status? 503) ;; => true
(retry/retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry/backoff-ms 2) ;; => long in [0, 400]
(retry/next-delay-ms 2 {"retry-after" "3"}) ;; => 3000
(retry/merge-opts {:max-attempts 5} true) ;; => {:max-attempts 5, ...}
Pure retry policy for transient HTTP failures in the Supabase Clojure SDK.
This namespace decides *whether* and *how long* to wait before retrying a
failed request. It performs no sleeping and no I/O: `supabase.core.http`
calls these functions from its retry loop and owns the actual waiting.
## Transient failures
A failure is worth retrying when it is likely to resolve on its own:
- HTTP statuses 429, 502, 503, 504 (`transient-status?`)
- Transport exceptions: connection refused, connect/read timeouts, no
route to host, reset or broken-pipe sockets, and unexpected EOF
(`transient-exception?`, which also walks the cause chain)
## Delay computation
Delays combine two sources, with the server always winning:
1. A `Retry-After` response header, when present and parseable
(`retry-after-ms`), either as delay-seconds or an RFC 1123 HTTP-date.
2. Exponential backoff with full jitter (`backoff-ms`), capped at
`:max-delay-ms`.
`next-delay-ms` applies that precedence. `merge-opts` resolves client-level
and request-level retry options against `default-opts`.
## Usage
(require '[supabase.core.retry :as retry])
(retry/transient-status? 503) ;; => true
(retry/retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry/backoff-ms 2) ;; => long in [0, 400]
(retry/next-delay-ms 2 {"retry-after" "3"}) ;; => 3000
(retry/merge-opts {:max-attempts 5} true) ;; => {:max-attempts 5, ...}Pluggable HTTP transport for the Supabase Clojure SDK.
All HTTP traffic is executed through an implementation of the
Transport protocol. The default implementation is a thin wrapper
around hato.client; service modules (auth, storage, postgrest,
functions, realtime) never call Hato directly.
Why a protocol? Three reasons:
HttpClient instance with custom timeouts, pool sizing, and
HTTP version selection.Transport and inject it via the
client map (:transport) or per request.A transport receives a lower-level request map produced by
supabase.core.http:
{:method :post
:url "https://abc.supabase.co/auth/v1/token"
:headers {"authorization" "Bearer ..." ...}
:query-params {"grant_type" "password"}
:body "{\"email\":\"a@b.com\"}" ;; string | bytes | File | InputStream
:multipart [{:name "file" :content #object[File ...]
:content-type "image/png" :file-name "a.png"}]
:as :string ;; :string | :byte-array | :stream | :reader
:timeout 30000} ;; optional
The transport must return a map with at minimum:
{:status 200
:headers {"content-type" "application/json" ...}
:body "{...}" ;; type depends on :as
:uri "..." ;; optional, but recommended for logging
:request {...}} ;; optional echo of the request for debugging
Exceptions are caught by supabase.core.http, not by the transport,
and converted to anomalies via supabase.core.error/from-exception.
execute-async returns a java.util.concurrent.CompletionStage (or
CompletableFuture) that resolves to a response map. The same
exception handling rules apply.
Pluggable HTTP transport for the Supabase Clojure SDK.
All HTTP traffic is executed through an implementation of the
[[Transport]] protocol. The default implementation is a thin wrapper
around `hato.client`; service modules (auth, storage, postgrest,
functions, realtime) never call Hato directly.
Why a protocol? Three reasons:
1. **Testability** — tests inject a fake transport that returns
canned responses without touching the network.
2. **Pooling** — a per-client transport can own a single
`HttpClient` instance with custom timeouts, pool sizing, and
HTTP version selection.
3. **Adapters** — callers who need clj-http, http-kit, or a custom
HTTP stack can implement [[Transport]] and inject it via the
client map (`:transport`) or per request.
## Request format
A transport receives a lower-level request map produced by
`supabase.core.http`:
{:method :post
:url "https://abc.supabase.co/auth/v1/token"
:headers {"authorization" "Bearer ..." ...}
:query-params {"grant_type" "password"}
:body "{\"email\":\"a@b.com\"}" ;; string | bytes | File | InputStream
:multipart [{:name "file" :content #object[File ...]
:content-type "image/png" :file-name "a.png"}]
:as :string ;; :string | :byte-array | :stream | :reader
:timeout 30000} ;; optional
## Response format
The transport must return a map with at minimum:
{:status 200
:headers {"content-type" "application/json" ...}
:body "{...}" ;; type depends on :as
:uri "..." ;; optional, but recommended for logging
:request {...}} ;; optional echo of the request for debugging
Exceptions are caught by `supabase.core.http`, not by the transport,
and converted to anomalies via `supabase.core.error/from-exception`.
## Async
`execute-async` returns a `java.util.concurrent.CompletionStage` (or
`CompletableFuture`) that resolves to a response map. The same
exception handling rules apply.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 |