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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supabase
4
+ module Realtime
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "realtime/version"
4
+ require_relative "realtime/errors"
5
+ require_relative "realtime/types"
6
+ require_relative "realtime/transformers"
7
+ require_relative "realtime/message"
8
+ require_relative "realtime/presence"
9
+ require_relative "realtime/push"
10
+ require_relative "realtime/socket"
11
+ require_relative "realtime/channel"
12
+ require_relative "realtime/client"
13
+ require_relative "realtime/test_socket"
14
+
15
+ module Supabase
16
+ module Realtime
17
+ end
18
+ end
@@ -0,0 +1,108 @@
1
+ # `supabase-storage`
2
+
3
+ Ruby client for [Supabase Storage](https://supabase.com/docs/guides/storage).
4
+ Bucket management, file upload/download, signed URLs, plus the Iceberg
5
+ (`analytics`) and vector bucket APIs. Mirrors the public surface of
6
+ [`storage3`](https://github.com/supabase/supabase-py/tree/main/src/storage)
7
+ in Python.
8
+
9
+ - Source: [github.com/supabase-rb/client](https://github.com/supabase-rb/client)
10
+
11
+ ## Installation
12
+
13
+ ```ruby
14
+ gem "supabase-storage"
15
+ ```
16
+
17
+ Then `bundle install`. (Requires Ruby >= 3.0.)
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require "supabase/storage"
23
+
24
+ storage = Supabase::Storage::Client.new(
25
+ base_url: "https://your-project.supabase.co/storage/v1",
26
+ headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
27
+ )
28
+ ```
29
+
30
+ ### Bucket management
31
+
32
+ ```ruby
33
+ storage.create_bucket("avatars", public: true)
34
+ storage.list_buckets
35
+ storage.get_bucket("avatars")
36
+ storage.update_bucket("avatars", public: false)
37
+ storage.empty_bucket("avatars")
38
+ storage.delete_bucket("avatars")
39
+ ```
40
+
41
+ ### File operations
42
+
43
+ Scoped to one bucket via `.from`:
44
+
45
+ ```ruby
46
+ bucket = storage.from("avatars")
47
+ bucket.upload("user1.png", File.binread("user1.png"), content_type: "image/png")
48
+ bucket.download("user1.png") # => bytes
49
+ bucket.list("folder/")
50
+ bucket.list_v2(prefix: "folder/", limit: 50, cursor: "abc", with_delimiter: true)
51
+ bucket.remove(["user1.png"])
52
+ bucket.move("user1.png", "archive/user1.png")
53
+ bucket.copy("user1.png", "backups/user1.png")
54
+ bucket.exists?("user1.png")
55
+ ```
56
+
57
+ Upload accepts `String` (raw bytes), any `IO`, `StringIO`, or `Pathname`.
58
+ Multipart encoding is handled by `faraday-multipart`. Metadata Hashes are
59
+ base64-encoded into the `x-metadata` header automatically.
60
+
61
+ ### Signed URLs
62
+
63
+ ```ruby
64
+ bucket.create_signed_url("user1.png", expires_in: 3600)
65
+ bucket.create_signed_urls(["user1.png", "user2.png"], expires_in: 3600)
66
+ bucket.get_public_url("user1.png")
67
+
68
+ # Signed upload URL — so a browser can upload directly to Storage
69
+ signed = bucket.create_signed_upload_url("user1.png")
70
+ bucket.upload_to_signed_url("user1.png", token: signed.token, file: bytes)
71
+ ```
72
+
73
+ ### Analytics (Iceberg) buckets
74
+
75
+ ```ruby
76
+ storage.analytics.create("warehouse")
77
+ storage.analytics.list
78
+ storage.analytics.delete("warehouse")
79
+ cfg = storage.analytics.catalog("warehouse",
80
+ access_key_id: "AKIA", secret_access_key: "...")
81
+ ```
82
+
83
+ ### Vector buckets
84
+
85
+ ```ruby
86
+ storage.vectors.create_bucket("embeddings")
87
+ storage.vectors.bucket("embeddings").create_index(
88
+ index_name: "docs", dimension: 1536, distance_metric: "cosine"
89
+ )
90
+ storage.vectors.bucket("embeddings").index("docs").put(records)
91
+ storage.vectors.bucket("embeddings").index("docs").query(vector, top_k: 10)
92
+ ```
93
+
94
+ ## Async variant
95
+
96
+ ```ruby
97
+ require "supabase/storage/async"
98
+
99
+ async = Supabase::Storage::Async::Client.new(
100
+ base_url: ENV["SUPABASE_URL"] + "/storage/v1",
101
+ headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
102
+ )
103
+
104
+ Async do
105
+ bucket = async.from("avatars")
106
+ data = bucket.download("user1.png")
107
+ end
108
+ ```
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "types"
5
+
6
+ module Supabase
7
+ module Storage
8
+ # Analytics (iceberg) bucket management. Mirrors storage3's
9
+ # `SyncStorageAnalyticsClient` — talks to `{storage_url}/iceberg/bucket`.
10
+ #
11
+ # client.analytics.create("my-iceberg")
12
+ # client.analytics.list(limit: 50, sort_column: "name", sort_order: "asc")
13
+ # client.analytics.delete("my-iceberg")
14
+ class AnalyticsClient
15
+ include Request
16
+
17
+ # @param session [Faraday::Connection]
18
+ # @param base_url [String] storage REST root (".../storage/v1/")
19
+ # @param headers [Hash]
20
+ def initialize(session, base_url, headers)
21
+ @session = session
22
+ normalized = base_url.end_with?("/") ? base_url : "#{base_url}/"
23
+ @base_url = "#{normalized}iceberg/"
24
+ @headers = headers
25
+ end
26
+
27
+ def create(bucket_name)
28
+ body = _request(:post, ["bucket"], json: { "name" => bucket_name })
29
+ Types::AnalyticsBucket.from_hash(body)
30
+ end
31
+
32
+ # Mirrors py's optional sort/search/pagination params. Nil values are
33
+ # dropped server-side; we also drop them client-side so the URL stays clean.
34
+ def list(limit: nil, offset: nil, sort_column: nil, sort_order: nil, search: nil)
35
+ params = { "limit" => limit, "offset" => offset,
36
+ "sort_column" => sort_column, "sort_order" => sort_order,
37
+ "search" => search }.compact
38
+ body = _request(:get, ["bucket"], query: params.empty? ? nil : params)
39
+ Array(body).map { |b| Types::AnalyticsBucket.from_hash(b) }
40
+ end
41
+
42
+ def delete(bucket_name)
43
+ body = _request(:delete, ["bucket", bucket_name])
44
+ Types::AnalyticsBucketDeleteResponse.from_hash(body)
45
+ end
46
+
47
+ # Python returns a `pyiceberg.RestCatalog`. There is no equivalent Iceberg
48
+ # client in the Ruby ecosystem, so we return the catalog configuration as
49
+ # a plain Hash that a downstream iceberg-ruby (when one exists) can consume.
50
+ # Keeping the public method present mirrors the API surface.
51
+ def catalog(catalog_name, access_key_id:, secret_access_key:)
52
+ service_key = @headers["apiKey"]
53
+ raise Errors::StorageApiError.new("apiKey must be passed in the headers.") if service_key.to_s.empty?
54
+
55
+ s3_endpoint = @base_url.sub(%r{iceberg/?\z}, "s3")
56
+ {
57
+ "name" => catalog_name,
58
+ "warehouse" => catalog_name,
59
+ "uri" => @base_url,
60
+ "token" => service_key,
61
+ "s3.endpoint" => s3_endpoint,
62
+ "s3.access-key-id" => access_key_id,
63
+ "s3.secret-access-key" => secret_access_key,
64
+ "s3.force-virtual-addressing" => "False"
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async/http/faraday"
4
+ require_relative "../client"
5
+
6
+ module Supabase
7
+ module Storage
8
+ module Async
9
+ # Async counterpart to {Supabase::Storage::Client}.
10
+ #
11
+ # Inherits the full public surface (list_buckets, get_bucket, create_bucket,
12
+ # update_bucket, empty_bucket, delete_bucket, from/bucket → FileApi) and
13
+ # rewires only the Faraday adapter to async-http-faraday so HTTP I/O yields
14
+ # back to the {::Async} reactor instead of blocking the thread.
15
+ #
16
+ # BucketApi and FileApi are transport-agnostic — they speak to whatever
17
+ # Faraday connection the client hands them, so neither has an async twin.
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/storage/async"
23
+ # require "async"
24
+ #
25
+ # storage = Supabase::Storage::Async::Client.new(
26
+ # base_url: "https://project.supabase.co/storage/v1",
27
+ # headers: { "apikey" => key }
28
+ # )
29
+ #
30
+ # Async do |task|
31
+ # uploads = files.map do |f|
32
+ # task.async { storage.from("avatars").upload(f.name, f.bytes) }
33
+ # end
34
+ # uploads.map(&:wait)
35
+ # end
36
+ class Client < Supabase::Storage::Client
37
+ private
38
+
39
+ def build_session(base_url)
40
+ Faraday.new(url: base_url, ssl: { verify: @verify }, proxy: @proxy) do |f|
41
+ f.request :multipart
42
+ if @timeout
43
+ f.options.timeout = @timeout
44
+ f.options.open_timeout = @timeout
45
+ end
46
+ f.adapter :async_http
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convenience requirer for the async tree, mirroring lib/supabase/postgrest/async.rb.
4
+ # Plain `require "supabase/storage"` stays free of async-http-faraday so sync-only
5
+ # consumers don't pay for the fiber stack.
6
+
7
+ require_relative "../storage"
8
+ require_relative "async/client"
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "types"
5
+ require_relative "utils"
6
+
7
+ module Supabase
8
+ module Storage
9
+ # The bucket-management half of the storage client — list / get / create / update /
10
+ # empty / delete on bucket records. Mirrors storage3's SyncStorageBucketAPI.
11
+ #
12
+ # {Client} inherits from this; callers don't construct BucketApi directly.
13
+ class BucketApi
14
+ include Request
15
+
16
+ # @param session [Faraday::Connection]
17
+ # @param base_url [String] storage REST endpoint, e.g. "https://x.supabase.co/storage/v1/"
18
+ # @param headers [Hash] static request headers (apikey, Authorization, etc.)
19
+ def initialize(session, base_url, headers)
20
+ @session = session
21
+ @base_url = base_url.end_with?("/") ? base_url : "#{base_url}/"
22
+ @headers = headers
23
+ end
24
+
25
+ def list_buckets
26
+ body = _request(:get, ["bucket"])
27
+ Array(body).map { |b| Types::Bucket.from_hash(b) }
28
+ end
29
+
30
+ def get_bucket(id)
31
+ Types::Bucket.from_hash(_request(:get, ["bucket", id]))
32
+ end
33
+
34
+ # @param id [String]
35
+ # @param name [String, nil] defaults to id
36
+ # @param public [Boolean, nil]
37
+ # @param file_size_limit [Integer, nil]
38
+ # @param allowed_mime_types [Array<String>, nil]
39
+ # @return [Hash] the raw response body
40
+ def create_bucket(id, name: nil, public: nil, file_size_limit: nil, allowed_mime_types: nil)
41
+ json = { "id" => id, "name" => name || id }
42
+ json["public"] = public unless public.nil?
43
+ json["file_size_limit"] = file_size_limit unless file_size_limit.nil?
44
+ json["allowed_mime_types"] = allowed_mime_types unless allowed_mime_types.nil?
45
+ _request(:post, ["bucket"], json: json)
46
+ end
47
+
48
+ def update_bucket(id, public: nil, file_size_limit: nil, allowed_mime_types: nil)
49
+ json = { "id" => id, "name" => id }
50
+ json["public"] = public unless public.nil?
51
+ json["file_size_limit"] = file_size_limit unless file_size_limit.nil?
52
+ json["allowed_mime_types"] = allowed_mime_types unless allowed_mime_types.nil?
53
+ _request(:put, ["bucket", id], json: json)
54
+ end
55
+
56
+ def empty_bucket(id)
57
+ _request(:post, ["bucket", id, "empty"], json: {})
58
+ end
59
+
60
+ def delete_bucket(id)
61
+ _request(:delete, ["bucket", id], json: {})
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+
6
+ require_relative "bucket_api"
7
+ require_relative "file_api"
8
+ require_relative "analytics"
9
+ require_relative "vectors"
10
+ require_relative "version"
11
+
12
+ module Supabase
13
+ module Storage
14
+ # Sync Storage client. Constructed once per project; reused across requests.
15
+ #
16
+ # client = Supabase::Storage::Client.new(
17
+ # base_url: "https://project.supabase.co/storage/v1",
18
+ # headers: { "apikey" => key, "Authorization" => "Bearer #{token}" }
19
+ # )
20
+ #
21
+ # client.list_buckets # bucket-management surface
22
+ # client.from("avatars").upload(...) # file-level surface, scoped to one bucket
23
+ class Client < BucketApi
24
+ attr_reader :base_url, :headers
25
+
26
+ # @param base_url [String] storage REST endpoint (e.g. ".../storage/v1")
27
+ # @param headers [Hash] static request headers (apikey, Authorization)
28
+ # @param http_client [Faraday::Connection, nil] inject a pre-built Faraday for tests
29
+ # @param verify [Boolean] TLS cert verification
30
+ # @param proxy [String, nil]
31
+ # @param timeout [Numeric, nil]
32
+ def initialize(base_url:, headers: {}, http_client: nil, verify: true, proxy: nil, timeout: nil)
33
+ @verify = verify
34
+ @proxy = proxy
35
+ @timeout = timeout
36
+ @http_client = http_client
37
+ normalized = base_url.to_s
38
+ normalized = "#{normalized}/" unless normalized.end_with?("/")
39
+
40
+ default_headers = {
41
+ "X-Client-Info" => "supabase-rb/storage-rb v#{VERSION}"
42
+ }.merge(headers)
43
+
44
+ super(http_client || build_session(normalized), normalized, default_headers)
45
+ end
46
+
47
+ # Return a {FileApi} scoped to the given bucket.
48
+ def from(bucket_id)
49
+ FileApi.new(bucket_id, @base_url, @headers, @session)
50
+ end
51
+
52
+ alias bucket from
53
+
54
+ # Iceberg / analytics bucket management. Mirrors storage3's
55
+ # `SyncStorageClient#analytics`.
56
+ def analytics
57
+ @analytics ||= AnalyticsClient.new(@session, @base_url, @headers)
58
+ end
59
+
60
+ # Vector bucket / index / record management. Mirrors storage3's
61
+ # `SyncStorageClient#vectors`.
62
+ def vectors
63
+ @vectors ||= VectorsClient.new(@session, @base_url, @headers)
64
+ end
65
+
66
+ private
67
+
68
+ def build_session(base_url)
69
+ Faraday.new(url: base_url, ssl: { verify: @verify }, proxy: @proxy) do |f|
70
+ f.request :multipart
71
+ if @timeout
72
+ f.options.timeout = @timeout
73
+ f.options.open_timeout = @timeout
74
+ end
75
+ f.adapter Faraday.default_adapter
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supabase
4
+ module Storage
5
+ module Errors
6
+ # Base class for any failure raised by the storage gem.
7
+ class StorageError < StandardError; end
8
+
9
+ # Raised when the Storage REST API returns a non-2xx response.
10
+ # Mirrors supabase-py's StorageApiError contract — exposes :message, :code (the
11
+ # API's `error` field, e.g. "InvalidKey"), and :status (HTTP status).
12
+ class StorageApiError < StorageError
13
+ attr_reader :code, :status
14
+
15
+ def initialize(message, code: nil, status: nil)
16
+ @code = code
17
+ @status = status
18
+ super(message.to_s)
19
+ end
20
+
21
+ def to_h
22
+ { "name" => "StorageApiError", "message" => message, "code" => @code, "status" => @status }
23
+ end
24
+ end
25
+
26
+ # Raised by the vectors client for client-side validation (e.g. batch size
27
+ # bounds). Mirrors storage3's VectorBucketException, which intentionally
28
+ # does NOT inherit from StorageError.
29
+ class VectorBucketException < StandardError; end
30
+ end
31
+ end
32
+ end