Public API for Supabase Realtime: WebSocket-based postgres_changes, broadcast, and presence subscriptions.
(require '[supabase.core.client :as sc]
'[supabase.realtime :as rt])
(def client (sc/make-client "https://abc.supabase.co" "anon-key"))
(def conn (rt/connect client {:on-error println}))
(def ch (rt/channel conn "room:lobby"
{:config {:broadcast {:self false}}}))
(rt/on ch :postgres-changes
{:event :insert :schema "public" :table "users"}
(fn [payload] (println "row" payload)))
(rt/on ch :broadcast {:event "typing"}
(fn [payload] (println "typing" payload)))
(rt/subscribe ch)
(rt/broadcast ch "typing" {:user "alice"})
(rt/track ch {:online_at (System/currentTimeMillis)})
(rt/unsubscribe ch)
(rt/disconnect conn)
In: postgres_changes, broadcast send/receive, basic presence,
manual set-auth, heartbeat, multi-channel per connection.
Out (deferred): broadcast ack/wait_for_ack, HTTP fallback, binary v2 protocol.
Public API for Supabase Realtime: WebSocket-based postgres_changes,
broadcast, and presence subscriptions.
## Quick start
(require '[supabase.core.client :as sc]
'[supabase.realtime :as rt])
(def client (sc/make-client "https://abc.supabase.co" "anon-key"))
(def conn (rt/connect client {:on-error println}))
(def ch (rt/channel conn "room:lobby"
{:config {:broadcast {:self false}}}))
(rt/on ch :postgres-changes
{:event :insert :schema "public" :table "users"}
(fn [payload] (println "row" payload)))
(rt/on ch :broadcast {:event "typing"}
(fn [payload] (println "typing" payload)))
(rt/subscribe ch)
(rt/broadcast ch "typing" {:user "alice"})
(rt/track ch {:online_at (System/currentTimeMillis)})
(rt/unsubscribe ch)
(rt/disconnect conn)
## v0.1.0 scope
In: postgres_changes, broadcast send/receive, basic presence,
manual `set-auth`, heartbeat, multi-channel per connection.
Out (deferred): broadcast ack/wait_for_ack, HTTP fallback,
binary v2 protocol.WebSocket connection lifecycle for Supabase Realtime.
Holds a single hato-backed WebSocket per connect call. State lives in
one atom; mutation goes through swap!; user callbacks run outside the
swap to avoid running user code under contention.
The Transport protocol is the test seam — tests substitute a recording
transport (see realtime_test) without redefining hato internals.
WebSocket connection lifecycle for Supabase Realtime. Holds a single hato-backed WebSocket per `connect` call. State lives in one atom; mutation goes through `swap!`; user callbacks run outside the swap to avoid running user code under contention. The `Transport` protocol is the test seam — tests substitute a recording transport (see `realtime_test`) without redefining hato internals.
Fluent builder for postgres_changes filter strings.
Each fn appends one column=operator.value condition; conditions are
joined with commas, which the Realtime server applies as AND (OR is not
supported). Pass the builder straight to supabase.realtime/on — it is
serialized to a string automatically — or call build yourself.
Mirrors realtime-js RealtimePostgresFilterBuilder: the operator surface
is the PostgREST subset the Realtime server evaluates (containment, range
and full-text operators are intentionally absent), values containing
reserved characters (, ( ) " \) or surrounding whitespace are
double-quoted and escaped the way PostgREST does, and any operator can be
negated with not.
(require '[supabase.realtime.filters :as f])
(-> (f/gt "amount" 100)
(f/not "status" :in ["draft" "archived"]))
;; builds: amount=gt.100,status=not.in.(draft,archived)
Fluent builder for postgres_changes `filter` strings.
Each fn appends one `column=operator.value` condition; conditions are
joined with commas, which the Realtime server applies as `AND` (OR is not
supported). Pass the builder straight to `supabase.realtime/on` — it is
serialized to a string automatically — or call `build` yourself.
Mirrors realtime-js `RealtimePostgresFilterBuilder`: the operator surface
is the PostgREST subset the Realtime server evaluates (containment, range
and full-text operators are intentionally absent), values containing
reserved characters (`,` `(` `)` `"` `\`) or surrounding whitespace are
double-quoted and escaped the way PostgREST does, and any operator can be
negated with `not`.
(require '[supabase.realtime.filters :as f])
(-> (f/gt "amount" 100)
(f/not "status" :in ["draft" "archived"]))
;; builds: amount=gt.100,status=not.in.(draft,archived)Phoenix Channel v1.0.0 JSON wire protocol for Supabase Realtime.
Pure builders + parser. No I/O, no state. The connection module composes these into a transport-bound conversation.
{:topic "realtime:room:lobby"
:event "phx_join"
:payload {...}
:ref "1"
:join_ref "1"} ; only on channel-bound messages
event-kind returns one of:
:phx-reply :phx-close :phx-error
:postgres-changes :broadcast
:presence-state :presence-diff
:system :heartbeat-reply :unknown
Phoenix Channel v1.0.0 JSON wire protocol for Supabase Realtime.
Pure builders + parser. No I/O, no state. The connection module composes
these into a transport-bound conversation.
## Frame shape
{:topic "realtime:room:lobby"
:event "phx_join"
:payload {...}
:ref "1"
:join_ref "1"} ; only on channel-bound messages
## Event kinds
`event-kind` returns one of:
:phx-reply :phx-close :phx-error
:postgres-changes :broadcast
:presence-state :presence-diff
:system :heartbeat-reply :unknownMalli schemas for Supabase Realtime API inputs.
Validates caller arguments only. Server payloads pass through unchecked — postgres column data is user-defined and shouldn't be gated.
Malli schemas for Supabase Realtime API inputs. Validates caller arguments only. Server payloads pass through unchecked — postgres column data is user-defined and shouldn't be gated.
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 |