Class: Supabase::Client
- Inherits:
-
Object
- Object
- Supabase::Client
- Defined in:
- lib/supabase/client.rb
Overview
Top-level client that combines every sub-library behind one object, mirroring supabase-py’s ‘supabase.create_client()`.
client = Supabase.create_client(
supabase_url: "https://project.supabase.co",
supabase_key: ENV["SUPABASE_ANON_KEY"]
)
client.auth.sign_in_with_password(email:, password:)
users = client.from("users").select("*").execute
client.storage.from("avatars").upload("a.png", bytes)
client.functions.invoke("hello-world", body: { name: "Ada" })
ch = client.realtime.channel("realtime:public:users")
Sub-clients are built lazily and memoized. Pass ‘async: true` to swap in the async-http-faraday variants for Auth / Postgrest / Storage / Functions; the Realtime client is transport-agnostic and ships sync regardless (a real WS transport is wired in by the caller — see lib/supabase/realtime/socket.rb).
Instance Attribute Summary collapse
-
#headers ⇒ Object
readonly
Returns the value of attribute headers.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#supabase_key ⇒ Object
readonly
Returns the value of attribute supabase_key.
-
#supabase_url ⇒ Object
readonly
Returns the value of attribute supabase_url.
Class Method Summary collapse
-
.create(supabase_url:, supabase_key:, options: nil, async: false) ⇒ Object
Mirrors supabase-py’s ‘Client.create(…)`: builds a client, then — if no explicit Authorization was supplied via options — tries to pull a persisted session via the auth client and applies its access_token as the bearer token.
Instance Method Summary collapse
- #async? ⇒ Boolean
-
#auth ⇒ Object
— Sub-clients ———————————————————.
-
#channel(topic, params: nil) ⇒ Object
Realtime shortcuts on the umbrella — mirror supabase-py so callers can do ‘client.channel(“public:users”)` instead of `client.realtime.channel(…)`.
- #from(table) ⇒ Object (also: #table)
- #functions ⇒ Object
- #get_channels ⇒ Object
-
#initialize(supabase_url:, supabase_key:, options: {}, async: false) ⇒ Client
constructor
A new instance of Client.
-
#postgrest ⇒ Object
PostgREST is the only sub-library where the public API is reached via a bare method on the umbrella (‘client.from(’users’)‘) rather than a named accessor.
- #realtime ⇒ Object
-
#remove_all_channels ⇒ Object
Unsubscribe every realtime channel registered on this client.
-
#remove_channel(channel) ⇒ Object
Unsubscribe a channel and drop it from the realtime registry.
- #rpc(func, params = {}, **opts) ⇒ Object
-
#schema(name) ⇒ Object
Return a Postgrest client scoped to ‘name` without mutating self.
-
#set_auth(token) ⇒ Object
Update the Authorization header used by every sub-client.
- #storage ⇒ Object
Constructor Details
#initialize(supabase_url:, supabase_key:, options: {}, async: false) ⇒ Client
Returns a new instance of Client.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/supabase/client.rb', line 65 def initialize(supabase_url:, supabase_key:, options: {}, async: false) # Use Supabase::SupabaseException once defined; fall back to ArgumentError # during early require cycles. Matches supabase-py's contract. err = defined?(Supabase::SupabaseException) ? Supabase::SupabaseException : ArgumentError raise err, "supabase_url is required" if supabase_url.to_s.empty? raise err, "supabase_key is required" if supabase_key.to_s.empty? raise err, "Invalid URL" unless supabase_url.to_s.match?(%r{^https?://.+}) @supabase_url = supabase_url.to_s.chomp("/") @supabase_key = supabase_key # Plain Hash → ClientOptions: paritet with supabase-py, where every # option flows through a typed dataclass. The legacy nested # `{ auth: {...}, postgrest: {...}, global: { headers: {...} } }` shape # is kept as a raw Hash so existing callers don't break — anything # else is canonicalized into a ClientOptions struct so the per-sub- # client kwargs derivation has one code path. legacy_hash_shape = .is_a?(Hash) && () warn_stray_legacy_keys() if legacy_hash_shape @options = if .is_a?(Hash) && !legacy_hash_shape ClientOptions.new(**.transform_keys(&:to_sym)) elsif .is_a?(Supabase::ClientOptions) # Mirror supabase-py's `self.options = copy.copy(options)` followed by # `self.options.headers = {**options.headers, ...}` — both the struct # and its headers hash become unique to this client, so a downstream # `client.options.headers["X"] = ...` mutation can't leak across # clients constructed from the same `ClientOptions` instance (F-C?). isolated = .dup isolated.headers = isolated.headers.dup isolated else end @async = async configured_headers = if @options.is_a?(Supabase::ClientOptions) @options.headers else @options[:global]&.dig(:headers) || @options.dig("global", "headers") || {} end @headers = { "apikey" => @supabase_key, "Authorization" => "Bearer #{@supabase_key}" }.merge(configured_headers || {}) # Current access token used to authorize the data-plane sub-clients # (postgrest/storage/functions) and the realtime socket. Starts as the # anon key and is rotated by #apply_auth on sign-in / token refresh. Held # explicitly (rather than re-derived from @headers) so that a realtime # client built LAZILY after a sign-in still picks up the session token # instead of the anon key — see #realtime. @access_token = @supabase_key end |
Instance Attribute Details
#headers ⇒ Object (readonly)
Returns the value of attribute headers.
31 32 33 |
# File 'lib/supabase/client.rb', line 31 def headers @headers end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
31 32 33 |
# File 'lib/supabase/client.rb', line 31 def @options end |
#supabase_key ⇒ Object (readonly)
Returns the value of attribute supabase_key.
31 32 33 |
# File 'lib/supabase/client.rb', line 31 def supabase_key @supabase_key end |
#supabase_url ⇒ Object (readonly)
Returns the value of attribute supabase_url.
31 32 33 |
# File 'lib/supabase/client.rb', line 31 def supabase_url @supabase_url end |
Class Method Details
.create(supabase_url:, supabase_key:, options: nil, async: false) ⇒ Object
Mirrors supabase-py’s ‘Client.create(…)`: builds a client, then — if no explicit Authorization was supplied via options — tries to pull a persisted session via the auth client and applies its access_token as the bearer token. Useful when bootstrapping from a session file the user previously signed into. Any error from get_session is swallowed so the client always returns successfully.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/supabase/client.rb', line 39 def self.create(supabase_url:, supabase_key:, options: nil, async: false) configured_auth = nil if .is_a?(Supabase::ClientOptions) configured_auth = .headers["Authorization"] || .headers[:Authorization] elsif .is_a?(Hash) configured_headers = [:global]&.dig(:headers) || .dig("global", "headers") || [:headers] || ["headers"] || {} configured_auth = configured_headers["Authorization"] || configured_headers[:Authorization] end client = new(supabase_url: supabase_url, supabase_key: supabase_key, options: || {}, async: async) if configured_auth.nil? begin session = client.auth.get_session client.set_auth(session.access_token) if session&.access_token rescue StandardError # No persisted session, or auth storage unavailable — fall back to # the apikey-only bearer that initialize set up. end end client end |
Instance Method Details
#async? ⇒ Boolean
122 123 124 |
# File 'lib/supabase/client.rb', line 122 def async? @async end |
#auth ⇒ Object
— Sub-clients ———————————————————
128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/supabase/client.rb', line 128 def auth return @auth if @auth @auth = auth_class.new(url: rest_url_for("auth/v1"), headers: @headers, **(:auth)) # Mirror supabase-py's `self.auth.on_auth_state_change(self._listen_to_auth_events)`: # when the auth client emits SIGNED_IN / TOKEN_REFRESHED / SIGNED_OUT, # propagate the new token to every other sub-client. @auth.on_auth_state_change do |event, session| next unless %w[SIGNED_IN TOKEN_REFRESHED SIGNED_OUT].include?(event) apply_auth(session&.access_token) end @auth end |
#channel(topic, params: nil) ⇒ Object
Realtime shortcuts on the umbrella — mirror supabase-py so callers can do ‘client.channel(“public:users”)` instead of `client.realtime.channel(…)`. The `realtime:` topic prefix is still optional (handled inside the Realtime client).
191 192 193 |
# File 'lib/supabase/client.rb', line 191 def channel(topic, params: nil) realtime.channel(topic, params: params) end |
#from(table) ⇒ Object Also known as: table
173 174 175 |
# File 'lib/supabase/client.rb', line 173 def from(table) postgrest.from(table) end |
#functions ⇒ Object
148 149 150 151 |
# File 'lib/supabase/client.rb', line 148 def functions @functions ||= functions_class.new(base_url: rest_url_for("functions/v1"), headers: @headers, **(:functions)) end |
#get_channels ⇒ Object
195 196 197 |
# File 'lib/supabase/client.rb', line 195 def get_channels realtime.get_channels end |
#postgrest ⇒ Object
PostgREST is the only sub-library where the public API is reached via a bare method on the umbrella (‘client.from(’users’)‘) rather than a named accessor. We expose both for explicitness.
168 169 170 171 |
# File 'lib/supabase/client.rb', line 168 def postgrest @postgrest ||= postgrest_class.new(base_url: rest_url_for("rest/v1"), headers: @headers, **(:postgrest)) end |
#realtime ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/supabase/client.rb', line 153 def realtime # Use the current access token (@access_token), not the anon key: if the # caller signed in before this lazy accessor first ran, apply_auth could # not push the token to a not-yet-built realtime client, so we must seed # the join auth from the rotated token here. apikey stays the anon key. @realtime ||= Realtime::Client.new( url: realtime_url, params: { "apikey" => @supabase_key, "access_token" => @access_token }, **(:realtime) ) end |
#remove_all_channels ⇒ Object
Unsubscribe every realtime channel registered on this client. Mirrors supabase-py’s ‘Client.remove_all_channels`; same sync/async contract as #remove_channel.
214 215 216 |
# File 'lib/supabase/client.rb', line 214 def remove_all_channels dispatch_realtime { realtime.remove_all_channels } end |
#remove_channel(channel) ⇒ Object
Unsubscribe a channel and drop it from the realtime registry. Mirrors supabase-py: the sync client blocks until the phx_leave frame is written; the async client (‘async def remove_channel`) lets callers await it. Under `async: true` we get the same shape via #dispatch_realtime — the call returns an `Async::Task` the caller may `.wait` on (US-050), so a slow `Socket#send` never stalls the calling fiber.
206 207 208 |
# File 'lib/supabase/client.rb', line 206 def remove_channel(channel) dispatch_realtime { realtime.remove_channel(channel) } end |
#rpc(func, params = {}, **opts) ⇒ Object
183 184 185 |
# File 'lib/supabase/client.rb', line 183 def rpc(func, params = {}, **opts) postgrest.rpc(func, params, **opts) end |
#schema(name) ⇒ Object
Return a Postgrest client scoped to ‘name` without mutating self. Matches supabase-py: `client.schema(“foo”).from_(“x”)` queries the foo schema but leaves `client.from(…)` (and other call sites) on the default schema.
221 222 223 |
# File 'lib/supabase/client.rb', line 221 def schema(name) postgrest.schema(name) end |
#set_auth(token) ⇒ Object
Update the Authorization header used by every sub-client. Useful after auth.sign_in returns a fresh JWT — the apikey stays the same but the bearer token becomes the user’s access token.
Breaking change vs <=3.1.1: ‘set_auth(nil)` no longer drops the memoized auth sub-client (and with it any persisted session). Call `auth.sign_out` to clear session state.
234 235 236 237 |
# File 'lib/supabase/client.rb', line 234 def set_auth(token) apply_auth(token) self end |
#storage ⇒ Object
143 144 145 146 |
# File 'lib/supabase/client.rb', line 143 def storage @storage ||= storage_class.new(base_url: rest_url_for("storage/v1"), headers: @headers, **(:storage)) end |