Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: supabase-community/supabase-ex
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.6.0
Choose a base ref
...
head repository: supabase-community/supabase-ex
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 7 commits
  • 19 files changed
  • 4 contributors

Commits on Feb 4, 2025

  1. chore: solve on_response unatural return (#60)

    ## Problem
    
    N/A
    
    ## Solution
    
    fixes #59 
    
    ## Rationale
    
    N/A
    zoedsoupe authored Feb 4, 2025
    Copy the full SHA
    2454654 View commit details

Commits on Feb 7, 2025

  1. Copy the full SHA
    16b1501 View commit details

Commits on May 2, 2025

  1. docs: update readme for version compatibility (#64)

    ## Problem
    
    The `~> 0.5` of the supabase-potion package caused version `0.6` to be
    pulled in. But `0.6` requires a major version upgrade of the postgrest
    package.
    
    ## Solution
    
    Update the docs to avoid version mismatches.
    barbinbrad authored May 2, 2025
    Copy the full SHA
    614fc39 View commit details

Commits on May 31, 2025

  1. fix: ex_doc linkings (#58)

    zoedsoupe authored May 31, 2025
    Copy the full SHA
    494e6c5 View commit details
  2. Copy the full SHA
    cd8138a View commit details

Commits on Jun 26, 2025

  1. Copy the full SHA
    523a225 View commit details

Commits on Jun 30, 2025

  1. docs: Simplify readme (#69)

    Ziinc authored Jun 30, 2025
    Copy the full SHA
    c9d02fe View commit details
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -31,14 +31,14 @@ jobs:
otp-version: ${{ matrix.otp }}

- name: Cache Elixir deps
uses: actions/cache@v1
uses: actions/cache@v4
id: deps-cache
with:
path: deps
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Cache Elixir _build
uses: actions/cache@v1
uses: actions/cache@v4
id: build-cache
with:
path: _build
@@ -86,14 +86,14 @@ jobs:
otp-version: ${{ matrix.otp }}

- name: Cache Elixir deps
uses: actions/cache@v1
uses: actions/cache@v4
id: deps-cache
with:
path: deps
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Cache Elixir _build
uses: actions/cache@v1
uses: actions/cache@v4
id: build-cache
with:
path: _build
@@ -158,14 +158,14 @@ jobs:
otp-version: ${{ matrix.otp }}

- name: Cache Elixir deps
uses: actions/cache@v1
uses: actions/cache@v4
id: deps-cache
with:
path: deps
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Cache Elixir _build
uses: actions/cache@v1
uses: actions/cache@v4
id: build-cache
with:
path: _build
1 change: 1 addition & 0 deletions .wakatime-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
supabase-ex
136 changes: 12 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,23 @@
# Supabase Potion
# Supabase Elixir

Where the magic starts!

> [!WARNING]
> This project is still in high development, expect breaking changes and unexpected behaviour.
## Getting Started

### Examples

This repository contains a few examples with sample apps to help you get started and showcase each usage of the client implementations:

#### Gotrue/Auth examples

TODO

<!--
- [Plug based auth](https://github.com/zoedsoupe/supabase-ex/tree/main/examples/auth/plug)
- [Phoenix LiveView based auth](https://github.com/zoedsoupe/supabase-ex/tree/main/examples/auth/phoenix_live_view)
- [User management](https://github.com/zoedsoupe/supabase-ex/tree/main/examples/auth/user_management)
-->

#### Storage examples

TODO

<!--
- [Plug based upload](https://github.com/zoedsoupe/supabase-ex/tree/main/examples/storage/plug)
- [Phoenix LiveView upload](https://github.com/zoedsoupe/supabase-ex/tree/main/examples/storage/phoenix_live_view)
-->

### Installation

To install the base SDK:
Supabase Community Elixir SDK

```elixir
def deps do
[
{:supabase_potion, "~> 0.5"}
{:supabase_potion, "~> 0.6"}, # base SDK
{:supabase_storage, "~> 0.4"}, # storage integration
{:supabase_gotrue, "~> 0.4"}, # auth integration
{:supabase_postgrest, "~> 1.0"}, # postgrest integration
]
end
```

### General usage
Individual product client documentation:

This library per si is the base foundation to user Supabase services from Elixir, so to integrate with specific services you need to add each client library you want to use.

Available client services are:
- [PostgREST](https://github.com/supabase-community/postgres-ex)
- [Storage](https://github.com/supabase-community/storage-ex)
- [Auth/GoTrue](https://github.com/supabase-community/auth-ex)

So if you wanna use the Storage and Auth/GoTrue services, your `mix.exs` should look like that:

```elixir
def deps do
[
{:supabase_potion, "~> 0.5"}, # base SDK
{:supabase_storage, "~> 0.3"}, # storage integration
{:supabase_gotrue, "~> 0.3"}, # auth integration
{:supabase_postgrest, "~> 0.2"}, # postgrest integration
]
end
```
- [Auth](https://github.com/supabase-community/auth-ex)

### Clients

@@ -111,36 +66,9 @@ iex> Supabase.init_client("https://<supabase-url>", "<supabase-api-key>",
iex> {:ok, %Supabase.Client{}}
```

> Note that one off clients are just raw elixir structs and therefore don't manage any state
For more information on the available options, see the [Supabase.Client](https://hexdocs.pm/supabase_potion/Supabase.Client.html) module documentation.

> There's also a bang version of `Supabase.init_client/3` that will raise an error if the client can't be created.
Initialized clients are elixir structs without any managed state.

You can also define a module that will centralize the client initialization:

```elixir
defmodule MyApp.Supabase.Client do
@behaviour Supabase.Client.Behaviour

@impl true
def init do
# your client initialization
# you should return {:ok, client} or {:error, reason}
# you probably want to use `Supabase.init_client/3` here
# but get the base_url and api_key from anywhere you want
end

@impl true
def get_client do
# your client retrieval
# you should return the client
# the management of the client state is up to you
end
end
```

For self managed clients, check the [next section](#self-managed-clients).
You can also implement the `Supabase.Client.Behaviour` callbacks to cntralize client init logic.

#### Self managed clients

@@ -193,49 +121,9 @@ defmodule MyApp.Application do
end
```

> Of course, you can spawn as many clients you wanna, with different configurations if you need
Now you can interact with the client process:
To interact with the client process:

```elixir
iex> {:ok, %Supabase.Client{} = client} = MyApp.Supabase.Client.get_client()
iex> {:ok, client} = MyApp.Supabase.Client.get_client()
iex> Supabase.GoTrue.sign_in_with_password(client, email: "", password: "")
```

You can also update the `access_token` for it:

```elixir
iex> {:ok, %Supabase.Client{} = client} = MyApp.Supabase.Client.get_client()
iex> client.access_token == client.api_key
iex> :ok = MyApp.Supabase.Client.set_auth("new-access-token")
iex> {:ok, %Supabase.Client{} = client} = MyApp.Supabase.Client.get_client()
iex> client.access_token == "new-access-token"
```

For more examples on how to use the client, check clients implementations docs:
- [Supabase.GoTrue](https://hexdocs.pm/supabase_gotrue)
- [Supabase.Storage](https://hexdocs.pm/supabase_storage)
- [Supabase.PostgREST](https://hexdocs.pm/supabase_postgrest)

### How to find my Supabase base URL?

You can find your Supabase base URL in the Settings page of your project.
Firstly select your project from the initial Dashboard.
On the left sidebar, click on the Settings icon, then select API.
The base URL is the first field on the page.

### How to find my Supabase API Key?

You can find your Supabase API key in the Settings page of your project.
Firstly select your project from the initial Dashboard.
On the left sidebar, click on the Settings icon, then select API.
The API key is the second field on the page.

There two types of API keys, the public and the private. The last one
bypass any Row Level Security (RLS) rules you have set up.
So you shouldn't use it in your frontend application.

If you don't know what RLS is, you can read more about it here:
https://supabase.com/docs/guides/auth/row-level-security

For most cases you should prefer to use the public "anon" Key.
2 changes: 1 addition & 1 deletion lib/supabase/error.ex
Original file line number Diff line number Diff line change
@@ -109,7 +109,7 @@ end

defmodule Supabase.HTTPErrorParser do
@moduledoc """
The default error parser in case no one is provided via `Supabase.Fetcher.with_error_parser/2`.
The default error parser in case no one is provided via `Supabase.Fetcher.Request.with_error_parser/2`.
Error parsers should be implement firstly by adjacent services libraries, to
handle service-specific error like for authentication or storage, although
11 changes: 4 additions & 7 deletions lib/supabase/fetcher.ex
Original file line number Diff line number Diff line change
@@ -165,7 +165,7 @@ defmodule Supabase.Fetcher do
@doc """
Executes the current request builder, asynchronously and returns the response.
Note that this function does not **stream** the request, although it can use
a stream to consume chunks, as you can see an example in `Supabase.Fetcher.Adapter.Finch.request_async/2`.
a stream to consume chunks, as you can see an example in `Supabase.Fetcher.Adapter.Finch`.
What happens here is that the request is done on a separate process, and the response
is sent via message passing, in chunks, so no all HTTP client support async requests.
@@ -218,10 +218,7 @@ defmodule Supabase.Fetcher do

def stream(%Request{http_client: http_client} = builder, on_response, opts)
when not is_nil(builder.url) do
with {:ok, resp} <- http_client.stream(builder, on_response, opts) do
{:ok, ResponseAdapter.from(resp)}
end
|> handle_response(builder)
http_client.stream(builder, on_response, opts)
rescue
exception -> handle_exception(exception, __STACKTRACE__, builder)
end
@@ -244,8 +241,8 @@ defmodule Supabase.Fetcher do
when response: Response.t(),
context: Request.t()
defp handle_response({:ok, %Response{} = resp}, %Request{} = builder) do
decode_body? = builder.options[:decode_body?] || true
parse_http_err? = builder.options[:parse_http_error?] || true
decode_body? = Keyword.get(builder.options, :decode_body?, true)
parse_http_err? = Keyword.get(builder.options, :parse_http_error?, true)
http_error_parser = builder.error_parser
decoder = builder.body_decoder
decoder_opts = builder.body_decoder_opts
7 changes: 6 additions & 1 deletion lib/supabase/fetcher/adapter/finch.ex
Original file line number Diff line number Diff line change
@@ -89,7 +89,12 @@ defmodule Supabase.Fetcher.Adapter.Finch do
end

if is_function(on_response, 1) do
on_response.({status, headers, stream})
case on_response.({status, headers, stream}) do
:ok -> :ok
{:ok, body} -> {:ok, body}
{:error, %Supabase.Error{} = err} -> {:error, err}
unexpected -> Supabase.Error.new(service: b.service, metadata: %{raw_error: unexpected})
end
else
%Finch.Response{
status: status,
14 changes: 11 additions & 3 deletions lib/supabase/fetcher/behaviour.ex
Original file line number Diff line number Diff line change
@@ -4,12 +4,20 @@ defmodule Supabase.Fetcher.Behaviour do
alias Supabase.Fetcher.Request
alias Supabase.Fetcher.Response

@type on_response_input ::
{Supabase.Fetcher.status(), Supabase.Fetcher.headers(), body :: Enumerable.t()}
@typedoc """
The response handler for streaming responses. It receives the response status, headers, and body as input.
Note that here only the status and headers are consumed from the stream and so the body reamins unconsumed for custom operations, receiving each chunk of the body as it arrives.
It needs to return either `:ok` or `{:ok, body}` or `{:error, Supabase.Error}`.
"""
@type on_response :: (on_response_input -> :ok | {:ok, term} | {:error, Supabase.Error.t()})

@callback request(Request.t()) :: Supabase.result(Response.t())
@callback request_async(Request.t()) :: Supabase.result(Response.t())
@callback upload(Request.t(), filepath :: Path.t()) :: Supabase.result(Response.t())
@callback stream(Request.t()) :: Supabase.result(Response.t())
@callback stream(Request.t(), on_response) :: Supabase.result(Response.t())
when on_response: ({Supabase.Fetcher.status(), Supabase.Fetcher.headers(),
body :: Enumerable.t()} ->
Supabase.result(Response.t()))
end
3 changes: 2 additions & 1 deletion lib/supabase/fetcher/request.ex
Original file line number Diff line number Diff line change
@@ -108,6 +108,7 @@ defmodule Supabase.Fetcher.Request do
headers =
global.headers
|> Map.put("authorization", "Bearer " <> client.access_token)
|> Map.put("apikey", client.api_key)
|> Map.to_list()

%__MODULE__{client: client, headers: headers}
@@ -133,7 +134,7 @@ defmodule Supabase.Fetcher.Request do
@doc """
Attaches a custom body decoder to be called after a successfull response.
The body decoder should implement the `Supabase.Fetcher.BodyDecoder` behaviour, and it default
to the `Supabase.Fetcher.JSONDecoder`, or it can be a 2-arity function that will follow the `Supabase.Fetcher.BodyDecoder.decode/1` callback interface.
to the `Supabase.Fetcher.JSONDecoder`, or it can be a 2-arity function that will follow the `Supabase.Fetcher.BodyDecoder.decode` callback interface.
You can pass `nil` as the decoder to avoid body decoding, if you need the raw body.
"""
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Supabase.MixProject do
use Mix.Project

@version "0.6.0"
@version "0.6.2"
@source_url "https://github.com/supabase-community/supabase-ex"

def project do
1 change: 1 addition & 0 deletions priv/local/client.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# local client to be used in tests and also
# dev env
defmodule SupabasePotion.Client do
@moduledoc false
use Supabase.Client, otp_app: :supabase_potion
end
36 changes: 33 additions & 3 deletions supabase/config.toml
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ refresh_token_reuse_interval = 10
# Allow/disallow new user signups to your project.
enable_signup = true
# Allow/disallow anonymous sign-ins to your project.
enable_anonymous_sign_ins = false
enable_anonymous_sign_ins = true
# Allow/disallow testing manual linking of accounts
enable_manual_linking = false
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
@@ -121,7 +121,7 @@ password_requirements = ""

[auth.email]
# Allow/disallow new user signups via email to your project.
enable_signup = false
enable_signup = true
# If enabled, a user will be required to confirm any email change on both the old, and new email
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = false
@@ -242,7 +242,7 @@ enabled = false
# user_pool_region = "us-east-1"

[edge_runtime]
enabled = false
enabled = true
# Configure one of the supported request policies: `oneshot`, `per_worker`.
# Use `oneshot` for hot reload, or `per_worker` for load testing.
policy = "oneshot"
@@ -258,6 +258,36 @@ inspector_port = 8083
# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
# entrypoint = "./functions/MY_FUNCTION_NAME/index.ts"

[functions.binary-data]
enabled = true
verify_jwt = false
entrypoint = "./functions/binary-data/index.ts"

[functions.error-cases]
enabled = true
verify_jwt = false
entrypoint = "./functions/error-cases/index.ts"

[functions.headers-demo]
enabled = true
verify_jwt = false
entrypoint = "./functions/headers-demo/index.ts"

[functions.json-echo]
enabled = true
verify_jwt = false
entrypoint = "./functions/json-echo/index.ts"

[functions.simple-text]
enabled = true
verify_jwt = false
entrypoint = "./functions/simple-text/index.ts"

[functions.stream-data]
enabled = true
verify_jwt = false
entrypoint = "./functions/stream-data/index.ts"

[analytics]
enabled = false
port = 54327
Loading