Class: Supabase::Client

Inherits:
Object
  • Object
show all
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.(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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(supabase_url:, supabase_key:, options: {}, async: false) ⇒ Client

Returns a new instance of Client.

Raises:

  • (err)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/supabase/client.rb', line 64

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
  @options      = options || {}
  @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 || {})
end

Instance Attribute Details

#headersObject (readonly)

Returns the value of attribute headers.



31
32
33
# File 'lib/supabase/client.rb', line 31

def headers
  @headers
end

#optionsObject (readonly)

Returns the value of attribute options.



31
32
33
# File 'lib/supabase/client.rb', line 31

def options
  @options
end

#supabase_keyObject (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_urlObject (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
# File 'lib/supabase/client.rb', line 39

def self.create(supabase_url:, supabase_key:, options: nil, async: false)
  configured_auth = nil
  if options.is_a?(Supabase::ClientOptions)
    configured_auth = options.headers["Authorization"] || options.headers[:Authorization]
  elsif options.is_a?(Hash)
    global_headers = options[:global]&.dig(:headers) || options.dig("global", "headers") || {}
    configured_auth = global_headers["Authorization"] || global_headers[:Authorization]
  end

  client = new(supabase_url: supabase_url, supabase_key: supabase_key,
               options: 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

Returns:

  • (Boolean)


90
91
92
# File 'lib/supabase/client.rb', line 90

def async?
  @async
end

#authObject

— Sub-clients ———————————————————



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/supabase/client.rb', line 96

def auth
  return @auth if @auth

  @auth = auth_class.new(url: rest_url_for("auth/v1"), headers: @headers, **sub_options(: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)

    token = session&.access_token || @supabase_key
    propagate_auth(token)
  end
  @auth
end

#from(table) ⇒ Object



138
139
140
# File 'lib/supabase/client.rb', line 138

def from(table)
  postgrest.from(table)
end

#functionsObject



117
118
119
120
# File 'lib/supabase/client.rb', line 117

def functions
  @functions ||= functions_class.new(base_url: rest_url_for("functions/v1"), headers: @headers,
                                     **sub_options(:functions))
end

#postgrestObject

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.



133
134
135
136
# File 'lib/supabase/client.rb', line 133

def postgrest
  @postgrest ||= postgrest_class.new(base_url: rest_url_for("rest/v1"), headers: @headers,
                                     **sub_options(:postgrest))
end

#realtimeObject



122
123
124
125
126
127
128
# File 'lib/supabase/client.rb', line 122

def realtime
  @realtime ||= Realtime::Client.new(
    url:    realtime_url,
    params: { "apikey" => @supabase_key, "access_token" => @supabase_key },
    **sub_options(:realtime)
  )
end

#rpc(func, params = {}, **opts) ⇒ Object



142
143
144
# File 'lib/supabase/client.rb', line 142

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.



149
150
151
# File 'lib/supabase/client.rb', line 149

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.



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/supabase/client.rb', line 158

def set_auth(token)
  @headers["Authorization"] = "Bearer #{token || @supabase_key}"
  # Reset memoized sub-clients so they pick up the new header on next access.
  # Realtime gets its own pathway (set_auth pushes access_token frames).
  @auth      = nil
  @storage   = nil
  @functions = nil
  @postgrest = nil
  @realtime&.set_auth(token)
  self
end

#storageObject



112
113
114
115
# File 'lib/supabase/client.rb', line 112

def storage
  @storage ||= storage_class.new(base_url: rest_url_for("storage/v1"), headers: @headers,
                                 **sub_options(:storage))
end