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.
- checksums.yaml +7 -0
- data/lib/supabase/README.md +90 -0
- data/lib/supabase/auth/README.md +172 -0
- data/lib/supabase/auth/admin_api.rb +218 -0
- data/lib/supabase/auth/admin_oauth_api.rb +51 -0
- data/lib/supabase/auth/api.rb +125 -0
- data/lib/supabase/auth/async/admin_api.rb +36 -0
- data/lib/supabase/auth/async/admin_oauth_api.rb +15 -0
- data/lib/supabase/auth/async/api.rb +32 -0
- data/lib/supabase/auth/async/client.rb +33 -0
- data/lib/supabase/auth/async.rb +14 -0
- data/lib/supabase/auth/client.rb +1217 -0
- data/lib/supabase/auth/constants.rb +32 -0
- data/lib/supabase/auth/errors.rb +207 -0
- data/lib/supabase/auth/helpers.rb +222 -0
- data/lib/supabase/auth/memory_storage.rb +25 -0
- data/lib/supabase/auth/storage.rb +19 -0
- data/lib/supabase/auth/timer.rb +40 -0
- data/lib/supabase/auth/types.rb +517 -0
- data/lib/supabase/auth/version.rb +7 -0
- data/lib/supabase/auth.rb +19 -0
- data/lib/supabase/client.rb +200 -0
- data/lib/supabase/client_options.rb +82 -0
- data/lib/supabase/functions/README.md +71 -0
- data/lib/supabase/functions/async/client.rb +45 -0
- data/lib/supabase/functions/async.rb +8 -0
- data/lib/supabase/functions/client.rb +174 -0
- data/lib/supabase/functions/errors.rb +38 -0
- data/lib/supabase/functions/types.rb +37 -0
- data/lib/supabase/functions/version.rb +7 -0
- data/lib/supabase/functions.rb +11 -0
- data/lib/supabase/postgrest/README.md +84 -0
- data/lib/supabase/postgrest/async/client.rb +50 -0
- data/lib/supabase/postgrest/async.rb +8 -0
- data/lib/supabase/postgrest/client.rb +136 -0
- data/lib/supabase/postgrest/errors.rb +49 -0
- data/lib/supabase/postgrest/request_builder.rb +657 -0
- data/lib/supabase/postgrest/types.rb +60 -0
- data/lib/supabase/postgrest/utils.rb +24 -0
- data/lib/supabase/postgrest/version.rb +7 -0
- data/lib/supabase/postgrest.rb +13 -0
- data/lib/supabase/realtime/README.md +90 -0
- data/lib/supabase/realtime/channel.rb +274 -0
- data/lib/supabase/realtime/client.rb +182 -0
- data/lib/supabase/realtime/errors.rb +19 -0
- data/lib/supabase/realtime/message.rb +38 -0
- data/lib/supabase/realtime/presence.rb +136 -0
- data/lib/supabase/realtime/push.rb +48 -0
- data/lib/supabase/realtime/socket.rb +40 -0
- data/lib/supabase/realtime/sockets/async_websocket.rb +175 -0
- data/lib/supabase/realtime/sockets/websocket_client_simple.rb +94 -0
- data/lib/supabase/realtime/test_socket.rb +65 -0
- data/lib/supabase/realtime/transformers.rb +26 -0
- data/lib/supabase/realtime/types.rb +70 -0
- data/lib/supabase/realtime/version.rb +7 -0
- data/lib/supabase/realtime.rb +18 -0
- data/lib/supabase/storage/README.md +108 -0
- data/lib/supabase/storage/analytics.rb +69 -0
- data/lib/supabase/storage/async/client.rb +52 -0
- data/lib/supabase/storage/async.rb +8 -0
- data/lib/supabase/storage/bucket_api.rb +65 -0
- data/lib/supabase/storage/client.rb +80 -0
- data/lib/supabase/storage/errors.rb +32 -0
- data/lib/supabase/storage/file_api.rb +281 -0
- data/lib/supabase/storage/request.rb +63 -0
- data/lib/supabase/storage/types.rb +236 -0
- data/lib/supabase/storage/utils.rb +35 -0
- data/lib/supabase/storage/vectors.rb +189 -0
- data/lib/supabase/storage/version.rb +7 -0
- data/lib/supabase/storage.rb +17 -0
- data/lib/supabase/version.rb +5 -0
- data/lib/supabase-auth.rb +3 -0
- data/lib/supabase.rb +63 -0
- 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
|