supabase-rb 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/lib/supabase/README.md +90 -0
  3. data/lib/supabase/auth/README.md +172 -0
  4. data/lib/supabase/auth/admin_api.rb +218 -0
  5. data/lib/supabase/auth/admin_oauth_api.rb +51 -0
  6. data/lib/supabase/auth/api.rb +125 -0
  7. data/lib/supabase/auth/async/admin_api.rb +36 -0
  8. data/lib/supabase/auth/async/admin_oauth_api.rb +15 -0
  9. data/lib/supabase/auth/async/api.rb +32 -0
  10. data/lib/supabase/auth/async/client.rb +33 -0
  11. data/lib/supabase/auth/async.rb +14 -0
  12. data/lib/supabase/auth/client.rb +1217 -0
  13. data/lib/supabase/auth/constants.rb +32 -0
  14. data/lib/supabase/auth/errors.rb +207 -0
  15. data/lib/supabase/auth/helpers.rb +222 -0
  16. data/lib/supabase/auth/memory_storage.rb +25 -0
  17. data/lib/supabase/auth/storage.rb +19 -0
  18. data/lib/supabase/auth/timer.rb +40 -0
  19. data/lib/supabase/auth/types.rb +517 -0
  20. data/lib/supabase/auth/version.rb +7 -0
  21. data/lib/supabase/auth.rb +19 -0
  22. data/lib/supabase/client.rb +200 -0
  23. data/lib/supabase/client_options.rb +82 -0
  24. data/lib/supabase/functions/README.md +71 -0
  25. data/lib/supabase/functions/async/client.rb +45 -0
  26. data/lib/supabase/functions/async.rb +8 -0
  27. data/lib/supabase/functions/client.rb +174 -0
  28. data/lib/supabase/functions/errors.rb +38 -0
  29. data/lib/supabase/functions/types.rb +37 -0
  30. data/lib/supabase/functions/version.rb +7 -0
  31. data/lib/supabase/functions.rb +11 -0
  32. data/lib/supabase/postgrest/README.md +84 -0
  33. data/lib/supabase/postgrest/async/client.rb +50 -0
  34. data/lib/supabase/postgrest/async.rb +8 -0
  35. data/lib/supabase/postgrest/client.rb +136 -0
  36. data/lib/supabase/postgrest/errors.rb +49 -0
  37. data/lib/supabase/postgrest/request_builder.rb +657 -0
  38. data/lib/supabase/postgrest/types.rb +60 -0
  39. data/lib/supabase/postgrest/utils.rb +24 -0
  40. data/lib/supabase/postgrest/version.rb +7 -0
  41. data/lib/supabase/postgrest.rb +13 -0
  42. data/lib/supabase/realtime/README.md +90 -0
  43. data/lib/supabase/realtime/channel.rb +274 -0
  44. data/lib/supabase/realtime/client.rb +182 -0
  45. data/lib/supabase/realtime/errors.rb +19 -0
  46. data/lib/supabase/realtime/message.rb +38 -0
  47. data/lib/supabase/realtime/presence.rb +136 -0
  48. data/lib/supabase/realtime/push.rb +48 -0
  49. data/lib/supabase/realtime/socket.rb +40 -0
  50. data/lib/supabase/realtime/sockets/async_websocket.rb +175 -0
  51. data/lib/supabase/realtime/sockets/websocket_client_simple.rb +94 -0
  52. data/lib/supabase/realtime/test_socket.rb +65 -0
  53. data/lib/supabase/realtime/transformers.rb +26 -0
  54. data/lib/supabase/realtime/types.rb +70 -0
  55. data/lib/supabase/realtime/version.rb +7 -0
  56. data/lib/supabase/realtime.rb +18 -0
  57. data/lib/supabase/storage/README.md +108 -0
  58. data/lib/supabase/storage/analytics.rb +69 -0
  59. data/lib/supabase/storage/async/client.rb +52 -0
  60. data/lib/supabase/storage/async.rb +8 -0
  61. data/lib/supabase/storage/bucket_api.rb +65 -0
  62. data/lib/supabase/storage/client.rb +80 -0
  63. data/lib/supabase/storage/errors.rb +32 -0
  64. data/lib/supabase/storage/file_api.rb +281 -0
  65. data/lib/supabase/storage/request.rb +63 -0
  66. data/lib/supabase/storage/types.rb +236 -0
  67. data/lib/supabase/storage/utils.rb +35 -0
  68. data/lib/supabase/storage/vectors.rb +189 -0
  69. data/lib/supabase/storage/version.rb +7 -0
  70. data/lib/supabase/storage.rb +17 -0
  71. data/lib/supabase/version.rb +5 -0
  72. data/lib/supabase-auth.rb +3 -0
  73. data/lib/supabase.rb +63 -0
  74. metadata +272 -0
@@ -0,0 +1,84 @@
1
+ # `supabase-postgrest`
2
+
3
+ Ruby client for [PostgREST](https://postgrest.org). Query builder for
4
+ PostgREST-backed Supabase tables, RPCs, and views. Mirrors the public surface
5
+ of [`postgrest`](https://github.com/supabase/supabase-py/tree/main/src/postgrest)
6
+ in Python.
7
+
8
+ - Source: [github.com/supabase-rb/client](https://github.com/supabase-rb/client)
9
+
10
+ ## Installation
11
+
12
+ ```ruby
13
+ gem "supabase-postgrest"
14
+ ```
15
+
16
+ Then `bundle install`. (Requires Ruby >= 3.0.)
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ require "supabase/postgrest"
22
+
23
+ client = Supabase::Postgrest::Client.new(
24
+ base_url: "https://your-project.supabase.co/rest/v1",
25
+ headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
26
+ )
27
+ ```
28
+
29
+ ### Select
30
+
31
+ ```ruby
32
+ users = client.from("users")
33
+ .select("id, name, email")
34
+ .eq("status", "active")
35
+ .gt("age", 18)
36
+ .order("created_at", desc: true)
37
+ .limit(10)
38
+ .execute
39
+ users.data # => [{ "id" => ..., "name" => ..., ... }, ...]
40
+ users.count # => populated when select(count: "exact")
41
+ ```
42
+
43
+ ### Insert / Upsert / Update / Delete
44
+
45
+ ```ruby
46
+ client.from("users").insert({ "name" => "Ada" }).execute
47
+ client.from("users").upsert({ "id" => 1, "name" => "Ada" }, on_conflict: "id").execute
48
+ client.from("users").update({ "name" => "Ada Lovelace" }).eq("id", 1).execute
49
+ client.from("users").delete.eq("id", 1).execute
50
+ ```
51
+
52
+ ### RPC
53
+
54
+ ```ruby
55
+ client.rpc("increment", { x: 1 }).execute
56
+ ```
57
+
58
+ ### Filters
59
+
60
+ Every PostgREST filter operator is supported: `eq`, `neq`, `gt/gte/lt/lte`,
61
+ `like` / `ilike` (+ `_all_of` / `_any_of` variants), `is_`, `in_`,
62
+ `contains` / `contained_by`, `range_lt/gt/gte/lte/adjacent`, `overlaps`,
63
+ `fts/plfts/phfts/wfts`, `match`, `or_`, `not_`. See
64
+ [`request_builder.rb`](request_builder.rb) for the full surface.
65
+
66
+ ## Async variant
67
+
68
+ ```ruby
69
+ require "supabase/postgrest/async"
70
+
71
+ async_client = Supabase::Postgrest::Async::Client.new(
72
+ base_url: ENV["SUPABASE_URL"] + "/rest/v1",
73
+ headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
74
+ )
75
+
76
+ Async do |task|
77
+ jobs = ids.map { |id| task.async { async_client.from("users").select("*").eq("id", id).execute } }
78
+ jobs.map(&:wait)
79
+ end
80
+ ```
81
+
82
+ A one-method override on top of `Postgrest::Client` that swaps in
83
+ [`async-http-faraday`](https://github.com/socketry/async-http-faraday) for
84
+ fiber-parallel I/O.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async/http/faraday"
4
+ require_relative "../client"
5
+
6
+ module Supabase
7
+ module Postgrest
8
+ module Async
9
+ # Async counterpart to {Supabase::Postgrest::Client}.
10
+ #
11
+ # Inherits the full public surface (from, table, rpc, schema) and rewires
12
+ # only the Faraday adapter to async-http-faraday so HTTP I/O yields back
13
+ # to the {::Async} reactor instead of blocking the thread.
14
+ #
15
+ # The request builders (RequestBuilder / FilterRequestBuilder / etc.) are
16
+ # transport-agnostic and reused unchanged — they speak to whatever Faraday
17
+ # connection the client hands them.
18
+ #
19
+ # Call sites must run inside an `Async do ... end` block; outside one, the
20
+ # adapter still works but loses the concurrency win.
21
+ #
22
+ # require "supabase/postgrest/async"
23
+ # require "async"
24
+ #
25
+ # client = Supabase::Postgrest::Async::Client.new(
26
+ # base_url: "https://project.supabase.co/rest/v1",
27
+ # headers: { "apikey" => key }
28
+ # )
29
+ #
30
+ # Async do |task|
31
+ # users_task = task.async { client.from("users").select("*").execute }
32
+ # posts_task = task.async { client.from("posts").select("*").execute }
33
+ # users, posts = users_task.wait, posts_task.wait
34
+ # end
35
+ class Client < Supabase::Postgrest::Client
36
+ private
37
+
38
+ def build_session
39
+ Faraday.new(url: @base_url, ssl: { verify: @verify }, proxy: @proxy) do |f|
40
+ if @timeout
41
+ f.options.timeout = @timeout
42
+ f.options.open_timeout = @timeout
43
+ end
44
+ f.adapter :async_http
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convenience requirer for the async tree, mirroring lib/supabase/auth/async.rb.
4
+ # Plain `require "supabase/postgrest"` stays free of async-http-faraday so
5
+ # sync-only consumers don't pay for the fiber stack.
6
+
7
+ require_relative "../postgrest"
8
+ require_relative "async/client"
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ require_relative "request_builder"
6
+ require_relative "version"
7
+
8
+ module Supabase
9
+ module Postgrest
10
+ DEFAULT_HEADERS = {
11
+ "Accept" => "application/json",
12
+ "Content-Type" => "application/json"
13
+ }.freeze
14
+
15
+ # Sync PostgREST client. Constructed once per project; reused across requests.
16
+ #
17
+ # ```ruby
18
+ # client = Supabase::Postgrest::Client.new(
19
+ # base_url: "https://project.supabase.co/rest/v1",
20
+ # headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
21
+ # )
22
+ #
23
+ # users = client.from("users").select("id, name").eq("status", "active").execute
24
+ # users.data # => [{ "id" => "...", "name" => "..." }, ...]
25
+ # users.count # => nil unless a count: was requested
26
+ # ```
27
+ class Client
28
+ attr_reader :base_url, :headers, :schema_name
29
+
30
+ # @param base_url [String] full URL to the PostgREST endpoint (e.g. ".../rest/v1")
31
+ # @param schema [String] postgres schema (default "public")
32
+ # @param headers [Hash] static request headers (apikey, Authorization)
33
+ # @param http_client [Faraday::Connection, nil] inject a pre-built Faraday for tests/custom adapters
34
+ # @param verify [Boolean] TLS cert verification
35
+ # @param proxy [String, nil] HTTP proxy URL
36
+ # @param timeout [Numeric, nil] per-request timeout (seconds)
37
+ def initialize(base_url:, schema: "public", headers: {}, http_client: nil,
38
+ verify: true, proxy: nil, timeout: nil)
39
+ @base_url = base_url.to_s.chomp("/")
40
+ @schema_name = schema
41
+ @headers = build_default_headers(schema, headers)
42
+ @http_client = http_client
43
+ @verify = verify
44
+ @proxy = proxy
45
+ @timeout = timeout
46
+ end
47
+
48
+ # Switch schemas. Returns a new client that points at a different postgres schema.
49
+ # @param name [String]
50
+ # @return [Client]
51
+ def schema(name)
52
+ # self.class so async subclasses return an async client, not a sync one.
53
+ self.class.new(
54
+ base_url: @base_url, schema: name,
55
+ headers: @headers.reject { |k, _| %w[Accept-Profile Content-Profile].include?(k) },
56
+ http_client: @http_client, verify: @verify, proxy: @proxy, timeout: @timeout
57
+ )
58
+ end
59
+
60
+ # @param table [String]
61
+ # @return [RequestBuilder] entry point for select/insert/update/upsert/delete on this table
62
+ def from(table)
63
+ RequestBuilder.new(session, "#{base_path}/#{table}", @headers.dup)
64
+ end
65
+
66
+ alias table from
67
+
68
+ # Stored procedure call.
69
+ # @param func [String] function name
70
+ # @param params [Hash] arguments to the function
71
+ # @param count [String, nil] one of "exact" / "planned" / "estimated"
72
+ # @param head [Boolean] HEAD method (count only, no body)
73
+ # @param get [Boolean] GET method (read-only)
74
+ # @return [RPCFilterRequestBuilder]
75
+ def rpc(func, params = {}, count: nil, head: false, get: false)
76
+ method = if head
77
+ "HEAD"
78
+ elsif get
79
+ "GET"
80
+ else
81
+ "POST"
82
+ end
83
+
84
+ headers = @headers.dup
85
+ headers["Prefer"] = "count=#{count}" if count
86
+
87
+ if %w[HEAD GET].include?(method)
88
+ query = stringify_keys(params)
89
+ body = nil
90
+ else
91
+ query = {}
92
+ body = params
93
+ end
94
+
95
+ request = RequestConfig.new(
96
+ session: session, path: "#{base_path}/rpc/#{func}",
97
+ http_method: method, headers: headers, params: query, json: body
98
+ )
99
+ RPCFilterRequestBuilder.new(request)
100
+ end
101
+
102
+ private
103
+
104
+ def build_default_headers(schema, user_headers)
105
+ defaults = DEFAULT_HEADERS.merge(
106
+ "X-Client-Info" => "supabase-rb/postgrest-rb v#{VERSION}"
107
+ )
108
+ defaults["Accept-Profile"] = schema
109
+ defaults["Content-Profile"] = schema
110
+ defaults.merge(user_headers)
111
+ end
112
+
113
+ def session
114
+ @session ||= @http_client || build_session
115
+ end
116
+
117
+ def build_session
118
+ Faraday.new(url: @base_url, ssl: { verify: @verify }, proxy: @proxy) do |f|
119
+ if @timeout
120
+ f.options.timeout = @timeout
121
+ f.options.open_timeout = @timeout
122
+ end
123
+ f.adapter Faraday.default_adapter
124
+ end
125
+ end
126
+
127
+ def base_path
128
+ URI.parse(@base_url).path.chomp("/")
129
+ end
130
+
131
+ def stringify_keys(hash)
132
+ hash.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supabase
4
+ module Postgrest
5
+ module Errors
6
+ # Raised when the PostgREST server returns a non-success status.
7
+ # Mirrors supabase-py's APIError — exposes :message, :code, :hint, :details
8
+ # plus the raw error hash via {#raw}.
9
+ class APIError < StandardError
10
+ attr_reader :raw, :message, :code, :hint, :details
11
+
12
+ # @param error [Hash] parsed JSON body from a PostgREST error response
13
+ def initialize(error = {})
14
+ @raw = error || {}
15
+ @message = @raw["message"] || @raw[:message]
16
+ @code = @raw["code"] || @raw[:code]
17
+ @hint = @raw["hint"] || @raw[:hint]
18
+ @details = @raw["details"] || @raw[:details]
19
+ super(to_s)
20
+ end
21
+
22
+ def to_s
23
+ parts = []
24
+ parts << "Error #{@code}:" if @code
25
+ parts << "\nMessage: #{@message}" if @message
26
+ parts << "\nHint: #{@hint}" if @hint
27
+ parts << "\nDetails: #{@details}" if @details
28
+ result = parts.join
29
+ result.empty? ? "Empty error" : result
30
+ end
31
+
32
+ # @return [Hash] the raw error payload as received
33
+ def json
34
+ @raw
35
+ end
36
+ end
37
+
38
+ # Builds a fallback error payload when the server body isn't valid JSON.
39
+ def self.generate_default_error_message(response)
40
+ {
41
+ "message" => "JSON could not be generated",
42
+ "code" => response.status.to_s,
43
+ "hint" => "Refer to full message for details",
44
+ "details" => response.body.to_s
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end