From 18d53758c7497e41c8bcda3607d3a02964c78dc8 Mon Sep 17 00:00:00 2001 From: Zoey de Souza Pessanha Date: Fri, 30 Aug 2024 00:52:34 -0300 Subject: [PATCH] feat: prepare for refactored minor version - remove unecessary and confuse option `manege_clients` - users should manage OTP process on their own - client struct should be simpler to use and manage - client initialization should be very explicit - no more infinite ways to "start" a client, just create a raw struct - remove old documentation - update elixir and erlang OTP source versions - remove usage of earthly on top of old github actions - add support for local dev with `asdf` - add local `.env.dev` for usage with supabase cli --- .env.dev | 2 + .github/workflows/ci.yml | 61 +++++------ .tool-versions | 2 + Earthfile | 33 ------ config/config.exs | 1 - flake.lock | 8 +- flake.nix | 8 +- guides/fetcher.md | 64 ----------- guides/storage.md | 56 ---------- lib/supabase.ex | 98 ++++++++--------- lib/supabase/application.ex | 10 +- lib/supabase/client.ex | 171 ++++++++++++++---------------- lib/supabase/client_registry.ex | 44 -------- lib/supabase/client_supervisor.ex | 54 ---------- lib/supabase/fetcher.ex | 15 ++- lib/supabase/supervisor.ex | 24 ----- lib/supabase/types/atom.ex | 30 ------ mix.exs | 9 +- mix.lock | 43 ++++---- test/supabase_test.exs | 36 +++---- 20 files changed, 218 insertions(+), 551 deletions(-) create mode 100644 .env.dev create mode 100644 .tool-versions delete mode 100644 Earthfile delete mode 100644 guides/fetcher.md delete mode 100644 guides/storage.md delete mode 100644 lib/supabase/client_registry.ex delete mode 100644 lib/supabase/client_supervisor.ex delete mode 100644 lib/supabase/supervisor.ex delete mode 100644 lib/supabase/types/atom.ex diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..2b1dd6d --- /dev/null +++ b/.env.dev @@ -0,0 +1,2 @@ +export SUPABASE_URL=http://127.0.0.1:54321 +export SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a87a3ef..911454d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,38 +2,39 @@ name: ci on: push: - branches: [ main ] + branches: + - main pull_request: - branches: [ main ] + branches: + - main jobs: - build: + lint: runs-on: ubuntu-latest - env: - GHCR_USERNAME: ${{ github.actor }} - GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} - FORCE_COLOR: 1 + + strategy: + matrix: + elixir: [1.17.0] + otp: [27.0] + steps: - - uses: actions/checkout@v3 - - name: Put back the git branch into git (Earthly uses it for tagging) - run: | - branch="" - if [ -n "$GITHUB_HEAD_REF" ]; then - branch="$GITHUB_HEAD_REF" - else - branch="${GITHUB_REF##*/}" - fi - git checkout -b "$branch" || true - - name: Docker Login - run: docker login https://ghcr.io --username "$GHCR_USERNAME" --password "$GHCR_TOKEN" - - name: Download latest earthly - run: "sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'" - - - name: Earthly version - run: earthly --version - - - name: Run CI - run: earthly -P +ci - - - name: Run Tests - run: earthly -P +test + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Install dependencies + run: mix deps.get + + - name: Clean build + run: mix clean + + - name: Check code formatting + run: mix format --check-formatted + + - name: Run Credo + run: mix credo --strict diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..7a9c1c3 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +erlang 27.0 +elixir 1.17.0-otp-27 diff --git a/Earthfile b/Earthfile deleted file mode 100644 index c695654..0000000 --- a/Earthfile +++ /dev/null @@ -1,33 +0,0 @@ -VERSION 0.7 - -ARG MIX_ENV=test - -deps: - ARG ELIXIR=1.15.7 - ARG OTP=26.1.2 - FROM hexpm/elixir:${ELIXIR}-erlang-${OTP}-alpine-3.17.5 - RUN apk add --no-cache build-base - WORKDIR /src - RUN mix local.rebar --force - RUN mix local.hex --force - COPY mix.exs mix.lock ./ - COPY --dir lib . # check .earthlyignore - RUN mix deps.get - RUN mix deps.compile --force - RUN mix compile - SAVE ARTIFACT /src/_build AS LOCAL _build - SAVE ARTIFACT /src/deps AS LOCAL deps - -ci: - FROM +deps - COPY .formatter.exs . - RUN mix clean - RUN mix compile --warning-as-errors - RUN mix format --check-formatted - RUN mix credo --strict - -test: - FROM +deps - COPY mix.exs mix.lock ./ - COPY --dir lib ./ - RUN mix test diff --git a/config/config.exs b/config/config.exs index 401c9b3..eb32525 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,7 +2,6 @@ import Config if config_env() == :dev do config :supabase_potion, - manage_clients: true, supabase_base_url: System.get_env("SUPABASE_URL"), supabase_api_key: System.get_env("SUPABASE_KEY") end diff --git a/flake.lock b/flake.lock index cd953c6..555c356 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1713564160, - "narHash": "sha256-YguPZpiejgzLEcO36/SZULjJQ55iWcjAmf3lYiyV1Fo=", + "lastModified": 1724819573, + "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bc194f70731cc5d2b046a6c1b3b15f170f05999c", + "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 1a40a10..98f6c92 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Supabase SDK for Elixir"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; systems.url = "github:nix-systems/default"; }; @@ -19,9 +19,9 @@ system, ... }: let - inherit (pkgs.beam.interpreters) erlangR26; + inherit (pkgs.beam.interpreters) erlang_27; inherit (pkgs.beam) packagesWith; - beam = packagesWith erlangR26; + beam = packagesWith erlang_27; in { _module.args.pkgs = import inputs.nixpkgs { inherit system; @@ -31,7 +31,7 @@ mkShell { name = "supabase-ex"; packages = with pkgs; - [beam.elixir_1_16] + [beam.elixir_1_17] ++ lib.optional stdenv.isLinux [inotify-tools] ++ lib.optional stdenv.isDarwin [ darwin.apple_sdk.frameworks.CoreServices diff --git a/guides/fetcher.md b/guides/fetcher.md deleted file mode 100644 index 3ac253d..0000000 --- a/guides/fetcher.md +++ /dev/null @@ -1,64 +0,0 @@ -# Supabase Fetcher - -The **Supabase Fetcher** is a versatile HTTP client that serves as an entry point for interacting with Supabase APIs from your Elixir applications. While it's often recommended to use higher-level APIs for specific Supabase services like [`Supabase.Storage`](https://github.com/zoedsoupe/supabase/tree/main/lib/supabase/storage.ex), this SDK provides low-level capabilities for fine-grained control and customization. - -## Overview - -This SDK allows you to make HTTP requests to Supabase and handle responses efficiently. It comes with several functions for common HTTP operations, such as GET, POST, PUT, DELETE, and file uploads. - -## Usage - -### Basic Request - -TODO - -### Streaming Large Files - -You can use `Supabase.Fetcher.stream/3` to make a GET request to a URL and stream back the response. This function is especially useful for streaming large file downloads. Custom `Finch` options can also be passed for more control over the request. - -```elixir -iex> {status, stream} = Supabase.Fetcher.stream("https://example.com") -iex> file = File.stream!("path/to/file", [], 4096) -Stream.run Stream.into(stream, file) -``` - -```elixir -iex> headers = [{"authorization", ""}] -iex> Supabase.Fetcher.stram("", headers, opts) # opts are passed directly to Finch.stream/5 -``` - -### Making HTTP Requests - -The SDK provides convenient functions for making common HTTP requests: - -- `Supabase.Fetcher.get/2`: Perform a GET request. -- `Supabase.Fetcher.post/3`: Perform a POST request. -- `Supabase.Fetcher.put/3`: Perform a PUT request. -- `Supabase.Fetcher.delete/3`: Perform a DELETE request. - -These functions format the response to a map or retrieve the error reason as a `String.t()`. - -### Uploading Files - -You can use `Supabase.Fetcher.upload/4` to upload a binary file to a URL using either a POST or PUT request. This is useful for file uploads in your applications. - -```elixir -iex> Supabase.Fetcher.upload(:post, "https://example.com/upload", "path/to/file") -``` - -### Headers and Authentication - -The SDK supports adding custom headers to requests. Additionally, you can use `Supabase.Fetcher.apply_headers/2` to conveniently set headers for authentication. It automatically includes the API key and, optionally, a token in the headers. - -```elixir -iex> headers = Supabase.Fetcher.apply_headers("apikey-value") -iex> Supabase.Fetcher.get("https://example.com", headers) -``` - -## Acknowledgements - -While the Supabase Fetcher Elixir SDK offers low-level control for making HTTP requests to Supabase, it is part of the broader Supabase ecosystem, which includes higher-level libraries for various Supabase services. - -## Additional Information - -For more details on using this package, refer to the [Supabase Fetcher documentation](https://hexdocs.pm/supabase_potion). diff --git a/guides/storage.md b/guides/storage.md deleted file mode 100644 index 1cce368..0000000 --- a/guides/storage.md +++ /dev/null @@ -1,56 +0,0 @@ -# Supabase Storage - -This module provides a set of Elixir functions that integrate seamlessly with Supabase's Storage API, allowing developers to perform various operations on buckets and objects. - -For more detailed information and specialized documentation, refers to: - -- [supabase_storage](https://github.com/zoedsoupe/supabase_storage) -- [supabase storage docs](https://hexdocs.pm/supabase_storage) - -### Features - -1. **Bucket Operations**: Easily create, list, empty, or remove buckets. -2. **Object Operations**: - - Upload & Download objects. - - Retrieve object information. - - Move, copy, or remove objects. - - Generate signed URLs for authenticated access. - - Stream download operations for efficient memory usage. - -### Usage - -Here are some examples of how you can use this package: - -#### Removing an object - -```elixir -Supabase.Storage.remove_object(conn, bucket, object) -``` - -#### Moving an object - -```elixir -Supabase.Storage.move_object(conn, bucket, object, destination) -``` - -#### Copying an object - -```elixir -Supabase.Storage.copy_object(conn, bucket, object, destination) -``` - -#### Uploading a file - -```elixir -Supabase.Storage.upload_object(conn, bucket, "avatars/some.png", "path/to/file.png") -``` - -#### Creating a signed URL - -```elixir -Supabase.Storage.create_signed_url(conn, bucket, "avatars/some.png", 3600) -``` - -### Permissions - -Ensure that the appropriate policy permissions are set in Supabase to carry out the required operations. Refer to each method's documentation for detailed information on permissions. diff --git a/lib/supabase.ex b/lib/supabase.ex index 7be2236..c7a071a 100644 --- a/lib/supabase.ex +++ b/lib/supabase.ex @@ -4,7 +4,7 @@ defmodule Supabase do ## Installation - The package can be installed by adding `supabase` to your list of dependencies in `mix.exs`: + The package can be installed by adding `supabase_potion` to your list of dependencies in `mix.exs`: def deps do [ @@ -28,15 +28,12 @@ defmodule Supabase do ### Clients - A `Supabase.Client` is an Agent that holds general information about Supabase, that can be used to intereact with any of the children integrations, for example: `Supabase.Storage` or `Supabase.UI`. - - Also a `Supabase.Client` holds a list of `Supabase.Connection` that can be used to perform operations on different buckets, for example. + A `Supabase.Client` holds general information about Supabase, that can be used to intereact with any of the children integrations, for example: `Supabase.Storage` or `Supabase.UI`. `Supabase.Client` is defined as: - - `:name` - the name of the client, started by `start_link/1` - `:conn` - connection information, the only required option as it is vital to the `Supabase.Client`. - - `:base_url` - The base url of the Supabase API, it is usually in the form `https://.supabase.io`. + - `:base_url` - The base url of the Supabase API, it is usually in the form `https://.supabase.co`. - `:api_key` - The API key used to authenticate requests to the Supabase API. - `:access_token` - Token with specific permissions to access the Supabase API, it is usually the same as the API key. - `:db` - default database options @@ -55,27 +52,21 @@ defmodule Supabase do ## Starting a Client - You then can start a Client calling `Supabase.Client.start_link/1`: - - iex> Supabase.Client.start_link(name: :my_client, client_info: %{db: %{schema: "public"}}) - {:ok, #PID<0.123.0>} + You then can start a Client calling `Supabase.init_client/1`: - Notice that this way to start a Client is not recommended, since you will need to manage the `Supabase.Client` manually. Instead, you can use `Supabase.init_client!/1`, passing the Client options: - - iex> Supabase.Client.init_client!(%{conn: %{base_url: "", api_key: ""}}) - {:ok, #PID<0.123.0>} + iex> Supabase.init_client(%{db: %{schema: "public"}}) + {:ok, %Supabase.Client{}} ## Acknowledgements - This package represents the complete SDK for Supabase. That means - that it includes all of the functionality of the Supabase client integrations, as: + This package represents the base SDK for Supabase. That means + that it not includes all of the functionality of the Supabase client integrations, so you need to install each feature separetely, as: - - `Supabase.Fetcher` - - `Supabase.Storage` - - `supabase-postgrest` - TODO - - `supabase-realtime` - TODO - - `supabase-auth`- TODO - - `supabase-ui` - TODO + - [auth](https://github.com/zoedsoupe/gotrue-ex) + - [storage](https://github.com/zoedsoupe/storage-ex) + - [postgrest](https://github.com/zoedsoupe/postgrest-ex) + - `realtime` - TODO + - `ui` - TODO ### Supabase Storage @@ -96,59 +87,62 @@ defmodule Supabase do ### Supabase UI Supabase UI is a set of UI components that help you quickly build Supabase-powered applications. It is built on top of Tailwind CSS and Headless UI, and is fully customizable. The package provides `Phoenix.LiveView` components! - - ### Supabase Fetcher - - Supabase Fetcher is a customized HTTP client for Supabase. Mainly used in Supabase Potion. If you want a complete control on how to make requests to any Supabase API, you would use this package directly. """ alias Supabase.Client - alias Supabase.ClientRegistry - alias Supabase.ClientSupervisor alias Supabase.MissingSupabaseConfig @typep changeset :: Ecto.Changeset.t() - @spec init_client(name :: atom, params) :: {:ok, pid} | {:error, changeset} - when params: Client.params() - def init_client(name, opts \\ %{}) do - conn = Map.get(opts, :conn, %{}) - opts = maybe_merge_config_from_application(conn, opts) - - with {:ok, opts} <- Client.parse(Map.put(opts, :name, name)) do - name = ClientRegistry.named(opts.name) - client_opts = [name: name, client_info: opts] - ClientSupervisor.start_child({Client, client_opts}) - end - rescue - _ -> Client.parse(opts) + @spec init_client(Client.params() | %{}) :: {:ok, Client.t()} | {:error, changeset} + def init_client(opts \\ %{}) do + opts + |> Map.get(:conn, %{}) + |> maybe_merge_config_from_application(opts) + |> Client.parse() end - def init_client!(name, %{} = opts \\ %{}) do + def init_client!(%{} = opts \\ %{}) do conn = Map.get(opts, :conn, %{}) opts = maybe_merge_config_from_application(conn, opts) - case init_client(name, opts) do - {:ok, pid} -> pid - {:error, changeset} -> raise Ecto.InvalidChangesetError, changeset: changeset, action: :init + case init_client(opts) do + {:ok, client} -> + client + + {:error, changeset} -> + errors = errors_on_changeset(changeset) + + if "can't be blank" in get_in(errors, [:conn, :api_key]) do + raise MissingSupabaseConfig, :key + end + + if "can't be blank" in get_in(errors, [:conn, :base_url]) do + raise MissingSupabaseConfig, :url + end + + raise Ecto.InvalidChangesetError, changeset: changeset, action: :init end end defp maybe_merge_config_from_application(%{base_url: _, api_key: _}, opts), do: opts defp maybe_merge_config_from_application(%{}, opts) do - base_url = - Application.get_env(:supabase_potion, :supabase_base_url) || - raise MissingSupabaseConfig, :url - - api_key = - Application.get_env(:supabase_potion, :supabase_api_key) || - raise MissingSupabaseConfig, :key + base_url = Application.get_env(:supabase_potion, :supabase_base_url) + api_key = Application.get_env(:supabase_potion, :supabase_api_key) Map.put(opts, :conn, %{base_url: base_url, api_key: api_key}) end + defp errors_on_changeset(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> + Regex.replace(~r"%{(\w+)}", message, fn _, key -> + opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() + end) + end) + end + defmacro __using__(which) when is_atom(which) do apply(__MODULE__, which, []) end diff --git a/lib/supabase/application.ex b/lib/supabase/application.ex index 6cb70e7..7d913be 100644 --- a/lib/supabase/application.ex +++ b/lib/supabase/application.ex @@ -7,15 +7,9 @@ defmodule Supabase.Application do @impl true def start(_start_type, _args) do - children = [{Finch, @finch_opts}, (if manage_clients?(), do: Supabase.Supervisor)] + children = [{Finch, @finch_opts}] opts = [strategy: :one_for_one, name: Supabase.Supervisor] - children - |> Enum.reject(&is_nil/1) - |> Supervisor.start_link(opts) - end - - defp manage_clients? do - Application.get_env(:supabase_potion, :manage_clients, true) + Supervisor.start_link(children, opts) end end diff --git a/lib/supabase/client.ex b/lib/supabase/client.ex index 1e5ae4d..101805a 100644 --- a/lib/supabase/client.ex +++ b/lib/supabase/client.ex @@ -1,42 +1,11 @@ defmodule Supabase.Client do @moduledoc """ A client for interacting with Supabase. This module is responsible for - managing the connection pool and the connection options. - - ## Usage - - Usually you don't need to use this module directly, instead you should - use the `Supabase` module, available on `:supabase_potion` application. - - However, if you want to manage clients manually, you can leverage this - module to start and stop clients dynamically. To start a single - client manually, you need to add it to your supervision tree: - - defmodule MyApp.Application do - use Application - - def start(_type, _args) do - children = [ - {Supabase.Client, name: :supabase, client_info: %Supabase.Client{}} - ] - - opts = [strategy: :one_for_one, name: MyApp.Supervisor] - Supervisor.start_link(children, opts) - end - end - - Notice that starting a Client in this way, Client options will not be - validated, so you need to make sure that the options are correct. Otherwise - application will crash. + managing the connection options for your Supabase project. ## Examples - iex> Supabase.Client.start_link(name: :supabase, client_info: client_info) - {:ok, #PID<0.123.0>} - - iex> Supabase.Client.retrieve_client(:supabase) %Supabase.Client{ - name: :supabase, conn: %{ base_url: "https://.supabase.io", api_key: "", @@ -59,7 +28,7 @@ defmodule Supabase.Client do } } - iex> Supabase.Client.retrieve_connection(:supabase) + iex> Supabase.Client.retrieve_connection(%Supabase.Client{}) %Supabase.Client.Conn{ base_url: "https://.supabase.io", api_key: "", @@ -67,7 +36,6 @@ defmodule Supabase.Client do } """ - use Agent use Ecto.Schema import Ecto.Changeset @@ -77,14 +45,7 @@ defmodule Supabase.Client do alias Supabase.Client.Db alias Supabase.Client.Global - alias Supabase.ClientRegistry - - defguard is_client(v) when is_atom(v) or is_pid(v) - - @type client :: atom | pid - @type t :: %__MODULE__{ - name: atom, conn: Conn.t(), db: Db.t(), global: Global.t(), @@ -100,24 +61,22 @@ defmodule Supabase.Client do @primary_key false embedded_schema do - field(:name, Supabase.Types.Atom) - embeds_one(:conn, Conn) embeds_one(:db, Db) embeds_one(:global, Global) embeds_one(:auth, Auth) end - @spec parse(params) :: {:ok, Supabase.Client.t()} | {:error, Ecto.Changeset.t()} + @spec parse(params) :: {:ok, t} | {:error, Ecto.Changeset.t()} def parse(attrs) do %__MODULE__{} - |> cast(attrs, [:name]) + |> cast(attrs, []) |> cast_embed(:conn, required: true) |> cast_embed(:db, required: false) |> cast_embed(:global, required: false) |> cast_embed(:auth, required: false) - |> validate_required([:name]) |> maybe_put_assocs() + |> validate_required([:conn]) |> apply_action(:parse) end @@ -150,77 +109,107 @@ defmodule Supabase.Client do defp maybe_put_assoc(changeset, _key, _assoc, _default), do: changeset - def start_link(config) do - name = Keyword.get(config, :name) - client_info = Keyword.get(config, :client_info) - - Agent.start_link(fn -> maybe_parse(client_info) end, name: name || __MODULE__) + @spec update_access_token(t, String.t()) :: t + def update_access_token(%__MODULE__{} = client, access_token) do + path = [Access.key(:conn), Access.key(:access_token)] + put_in(client, path, access_token) end - defp maybe_parse(%__MODULE__{} = client), do: client - defp maybe_parse(params), do: parse!(params) - - @spec retrieve_client(name) :: {:ok, Supabase.Client.t()} | {:error, :client_not_started} - when name: atom | pid - def retrieve_client(source) do - if is_atom(source) do - if pid = ClientRegistry.lookup(source) do - {:ok, Agent.get(pid, & &1)} - else - {:ok, Agent.get(source, & &1)} - end - else - {:ok, Agent.get(source, & &1)} - end - rescue - _ -> {:error, :client_not_started} - end + @doc """ + Given a `Supabase.Client`, return the connection informations. - @spec retrieve_connection(name) :: {:ok, Conn.t()} | {:error, :client_not_started} - when name: atom | pid - def retrieve_connection(source) do - with {:ok, client} <- retrieve_client(source) do - client.conn - end - end + ## Examples - def update_access_token(source, access_token) when is_atom(source) do - if pid = ClientRegistry.lookup(source) do - path = [Access.key(:conn), Access.key(:access_token)] - Agent.update(pid, &put_in(&1, path, access_token)) - else - {:error, :client_not_started} - end - end + iex> Supabase.Client.retrieve_connection(%Supabase.Client{}) + %Supabase.Client.Conn{} + """ + @spec retrieve_connection(t) :: Conn.t() + def retrieve_connection(%__MODULE__{conn: conn}), do: conn - def update_access_token(source, access_token) when is_pid(source) do - path = [Access.key(:conn), Access.key(:access_token)] - Agent.update(source, &put_in(&1, path, access_token)) - rescue - _ -> {:error, :client_not_started} - end + @doc """ + Given a `Supabase.Client`, return the raw the base url for the Supabase project. + ## Examples + + iex> Supabase.Client.retrieve_base_url(%Supabase.Client{}) + "https://.supabase.co" + """ + @spec retrieve_base_url(t) :: String.t() def retrieve_base_url(%__MODULE__{conn: conn}) do conn.base_url end - def retrieve_url(%__MODULE__{} = client, uri) do + @spec retrieve_url(t, String.t()) :: URI.t() + defp retrieve_url(%__MODULE__{} = client, uri) do client |> retrieve_base_url() |> URI.merge(uri) end + @doc """ + Given a `Supabase.Client`, mounts the base url for the Auth/GoTrue feature. + + ## Examples + + iex> Supabase.Client.retrieve_auth_url(%Supabase.Client{}) + "https://.supabase.co/auth/v1" + """ + @spec retrieve_auth_url(t, String.t()) :: String.t() def retrieve_auth_url(%__MODULE__{auth: auth} = client, uri \\ "/") do client |> retrieve_url(auth.uri) |> URI.append_path(uri) + |> URI.to_string() end @storage_endpoint "/storage/v1" + @doc """ + Given a `Supabase.Client`, mounts the base url for the Storage feature. + + ## Examples + + iex> Supabase.Client.retrieve_storage_url(%Supabase.Client{}) + "https://.supabase.co/storage/v1" + """ + @spec retrieve_storage_url(t, String.t()) :: String.t() def retrieve_storage_url(%__MODULE__{} = client, uri \\ "/") do client |> retrieve_url(@storage_endpoint) |> URI.append_path(uri) + |> URI.to_string() + end + + defimpl Inspect, for: Supabase.Client do + import Inspect.Algebra + + def inspect(%Supabase.Client{} = client, opts) do + concat([ + "#Supabase.Client<", + nest( + concat([ + line(), + "base_url: ", + to_doc(client.conn.base_url, opts), + ",", + line(), + "schema: ", + to_doc(client.db.schema, opts), + ",", + line(), + "auth: (", + "flow_type: ", + to_doc(client.auth.flow_type, opts), + ", ", + "persist_session: ", + to_doc(client.auth.persist_session, opts), + ")" + ]), + 2 + ), + line(), + ">" + ]) + end end end diff --git a/lib/supabase/client_registry.ex b/lib/supabase/client_registry.ex deleted file mode 100644 index 1a9c027..0000000 --- a/lib/supabase/client_registry.ex +++ /dev/null @@ -1,44 +0,0 @@ -defmodule Supabase.ClientRegistry do - @moduledoc """ - Registry for the Supabase multiple Clients. This registry is used to - register and lookup the Supabase Clients defined by the user. - - This Registry is used by the `Supabase.ClientSupervisor` to register and - any `Supabase.Client` that is defined. That way, the `Supabase.ClientSupervisor` - can lookup the `Supabase.Client` by name and start it if it is not running. - - ## Usage - - This Registry is used internally by the `Supabase.Application` and should - start automatically when the application starts. - """ - - def start_link(_) do - Registry.start_link(keys: :unique, name: __MODULE__) - end - - def child_spec(opts) do - %{ - id: __MODULE__, - start: {__MODULE__, :start_link, [opts]}, - type: :worker, - restart: :permanent, - shutdown: 500 - } - end - - def named(key) when is_atom(key) do - {:via, Registry, {__MODULE__, key}} - end - - def register(key) when is_atom(key) do - Registry.register(__MODULE__, key, nil) - end - - def lookup(key) when is_atom(key) do - case Registry.lookup(__MODULE__, key) do - [{pid, _}] -> pid - [] -> nil - end - end -end diff --git a/lib/supabase/client_supervisor.ex b/lib/supabase/client_supervisor.ex deleted file mode 100644 index 6f96099..0000000 --- a/lib/supabase/client_supervisor.ex +++ /dev/null @@ -1,54 +0,0 @@ -defmodule Supabase.ClientSupervisor do - @moduledoc """ - A supervisor for all Clients. In most cases this should be started - automatically by the application supervisor and be used mainly by - the `Supabase` module, available on `:supabase_potion` application. - - Although if you want to manage Clients manually, you can leverage - this module to start and stop Clients dynamically. To start the supervisor - manually, you need to add it to your supervision tree: - - defmodule MyApp.Application do - use Application - - def start(_type, _args) do - children = [ - {Supabase.ClientSupervisor, []} - ] - - opts = [strategy: :one_for_one, name: MyApp.Supervisor] - Supervisor.start_link(children, opts) - end - end - - And then use the Supervisor to start custom clients: - - iex> Supabase.ClientSupervisor.start_child({Supabase.Client, opts}) - {:ok, #PID<0.123.0>} - - Notice that the Supabase Elixir SDK already starts a `Supabase.ClientSupervisor` - internally, so you don't need to start it manually. However, if you want to - manage clients manually, you can leverage this module to start and stop - clients dynamically. - - To manage manually the clients, you need to disable the internal management - into your application: - - config :supabase, :manage_clients, false - """ - - use DynamicSupervisor - - @impl true - def init(_init_arg) do - DynamicSupervisor.init(strategy: :one_for_one) - end - - def start_link(init) do - DynamicSupervisor.start_link(__MODULE__, init, name: __MODULE__) - end - - def start_child(child_spec) do - DynamicSupervisor.start_child(__MODULE__, child_spec) - end -end diff --git a/lib/supabase/fetcher.ex b/lib/supabase/fetcher.ex index 202c319..3d411af 100644 --- a/lib/supabase/fetcher.ex +++ b/lib/supabase/fetcher.ex @@ -238,7 +238,7 @@ defmodule Supabase.Fetcher do """ @impl true def upload(method, url, file, headers \\ []) do - body_stream = File.stream!(file, [{:read_ahead, 4096}], 1024) + body_stream = File.stream!(file, 4096, encoding: :utf8) %File.Stat{size: content_length} = File.stat!(file) content_headers = [{"content-length", to_string(content_length)}] headers = merge_headers(headers, content_headers) @@ -320,17 +320,13 @@ defmodule Supabase.Fetcher do end def format_response({:ok, %{status: s, body: body}}) when s in 200..300 do - result = - case Jason.decode(body) do - {:ok, body} -> body - {:error, _} when is_binary(body) -> body - end - - {:ok, result} + Jason.decode(body) end def format_response({:ok, %{status: s, body: body}}) when s in 400..499 do - {:error, format_bad_request_error(Jason.decode!(body))} + with {:ok, result} <- Jason.decode(body) do + {:error, format_bad_request_error(result)} + end end def format_response({:ok, %{status: s}}) when s >= 500 do @@ -351,6 +347,7 @@ defmodule Supabase.Fetcher do else case msg do "Email rate limit exceeded" -> :email_rate_limit + _ -> msg end end end diff --git a/lib/supabase/supervisor.ex b/lib/supabase/supervisor.ex deleted file mode 100644 index f5bfa5a..0000000 --- a/lib/supabase/supervisor.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Supabase.Supervisor do - @moduledoc """ - This module is reponsable to start the `Supabase.ClientRegistry` - and `Supabase.ClientSupervisor` processes to manage clients - automatically, - """ - - use Supervisor - - def start_link(opts \\ []) do - Supervisor.start_link(__MODULE__, opts) - end - - @impl true - def init(opts) do - name = Keyword.get(opts, :name, Supabase.Supervisor) - strategy = Keyword.get(opts, :strategy, :one_for_one) - opts = [name: name, strategy: strategy] - children = [Supabase.ClientRegistry, Supabase.ClientSupervisor] - - Supervisor.init(children, opts) - end - -end diff --git a/lib/supabase/types/atom.ex b/lib/supabase/types/atom.ex deleted file mode 100644 index 940a082..0000000 --- a/lib/supabase/types/atom.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Supabase.Types.Atom do - @moduledoc """ - A custom type for Ecto that allows atoms to be used as fields in schemas. - """ - - use Ecto.ParameterizedType - - @impl true - def type(_), do: :string - - @impl true - def init(_opts) do - [] - end - - @impl true - def cast(v, _opts) when is_atom(v), do: {:ok, v} - - def cast(v, _opts) when is_binary(v), - do: {:ok, Module.concat(Elixir, String.to_existing_atom(v))} - - @impl true - def dump(v, _opts, _) when is_atom(v), do: {:ok, Atom.to_string(v)} - - @impl true - def load(v, _opts, _) when is_binary(v), - do: {:ok, Module.concat(Elixir, String.to_existing_atom(v))} - - def load(v, _opts, _), do: {:ok, v} -end diff --git a/mix.exs b/mix.exs index a55fb10..9d9cbe6 100644 --- a/mix.exs +++ b/mix.exs @@ -1,16 +1,13 @@ -defmodule Supabase.Potion.MixProject do +defmodule Supabase.MixProject do use Mix.Project - @version "0.3.7" - @source_url "https://github.com/zoedsoupe/supabase" + @version "0.4.0" + @source_url "https://github.com/zoedsoupe/supabase-ex" def project do [ app: :supabase_potion, version: @version, - build_path: "../../_build", - deps_path: "../../deps", - lockfile: "../../mix.lock", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/mix.lock b/mix.lock index 675977b..ef914d5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,28 +1,29 @@ %{ - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "multipart": {:hex, :multipart, "0.1.1", "952c6aeb41c762d1ea9776c891754cfb61962c9d7b0f84fb63454779910b379d", [:mix], [{:mime, "~> 1.2", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "bc349da107810c220ef0366724e445a1a2a39e6be3a361c6a141e0d507eee157"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "peri": {:hex, :peri, "0.2.9", "1f83c04a2957d354221462468a0435ef0ed581505794be43c8cf39c7e9779158", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "2929a65cc45d432dc13dc8348f270a6c5dcaec8f361f3236a0208b3976a78bc9"}, "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, @@ -32,5 +33,5 @@ "supabase_potion": {:hex, :supabase_potion, "0.3.0", "074719bc46a37ded57e55bb6c24c80b5f9479f75cee8b5c6be864702b69a0216", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9546a3d0854e4a97b4ed3efba37775f4f9ec266d7d3585d88a6d484b0ef5aa9"}, "supabase_storage": {:hex, :supabase_storage, "0.1.0", "58b71070de6232724fb5ead406a7e35bdfca97a2f4e3733b0f13d4dd26c2eb40", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:supabase_connection, "~> 0.1", [hex: :supabase_connection, repo: "hexpm", optional: false]}, {:supabase_fetcher, "~> 0.1", [hex: :supabase_fetcher, repo: "hexpm", optional: false]}], "hexpm", "4b8343f8b0c39633bcf8ae7a82b4c92ab22cac66f67bf7bbb2cb948072c192e9"}, "supabase_types": {:hex, :supabase_types, "0.1.1", "97cb4abaf4ce28cddcd1afb8eb3e0f5e7ba439db3c3288f775d7a1cd8be18b37", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "a8cc84753fdd160f4db4ea31a3c92b60c5efea2d6153a11da19e02943433e42f"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } diff --git a/test/supabase_test.exs b/test/supabase_test.exs index 896b336..91b8e8f 100644 --- a/test/supabase_test.exs +++ b/test/supabase_test.exs @@ -1,37 +1,36 @@ defmodule SupabaseTest do use ExUnit.Case, async: true + import Ecto.Changeset + alias Supabase.Client - alias Supabase.ClientRegistry alias Supabase.MissingSupabaseConfig describe "init_client/1" do - test "should return a valid PID on valid attrs" do - {:ok, pid} = - Supabase.init_client(:test, %{ + test "should return a valid client on valid attrs" do + {:ok, %Client{} = client} = + Supabase.init_client(%{ conn: %{ base_url: "https://test.supabase.co", api_key: "test" } }) - assert pid == ClientRegistry.lookup(:test) - assert {:ok, client} = Client.retrieve_client(:test) - assert client.name == :test assert client.conn.base_url == "https://test.supabase.co" assert client.conn.api_key == "test" end test "should return an error changeset on invalid attrs" do {:error, changeset} = Supabase.init_client(%{}) + conn = get_change(changeset, :conn) - assert changeset.errors == [ - name: {"can't be blank", [validation: :required]}, - conn: {"can't be blank", [validation: :required]} + assert conn.errors == [ + api_key: {"can't be blank", [validation: :required]}, + base_url: {"can't be blank", [validation: :required]} ] - {:error, changeset} = Supabase.init_client(:test, %{conn: %{}}) - assert conn = changeset.changes.conn + {:error, changeset} = Supabase.init_client(%{conn: %{}}) + conn = get_change(changeset, :conn) assert conn.errors == [ api_key: {"can't be blank", [validation: :required]}, @@ -41,31 +40,28 @@ defmodule SupabaseTest do end describe "init_client!/1" do - test "should return a valid PID on valid attrs" do - pid = - Supabase.init_client!(:test2, %{ + test "should return a valid client on valid attrs" do + assert %Client{} = client = + Supabase.init_client!(%{ conn: %{ base_url: "https://test.supabase.co", api_key: "test" } }) - assert pid == ClientRegistry.lookup(:test2) - assert {:ok, client} = Client.retrieve_client(:test2) - assert client.name == :test2 assert client.conn.base_url == "https://test.supabase.co" assert client.conn.api_key == "test" end test "should raise MissingSupabaseConfig on missing base_url" do assert_raise MissingSupabaseConfig, fn -> - Supabase.init_client!(:test, %{conn: %{api_key: "test"}}) + Supabase.init_client!(%{conn: %{api_key: "test"}}) end end test "should raise MissingSupabaseConfig on missing api_key" do assert_raise MissingSupabaseConfig, fn -> - Supabase.init_client!(:test, %{conn: %{base_url: "https://test.supabase.co"}}) + Supabase.init_client!(%{conn: %{base_url: "https://test.supabase.co"}}) end end end