Skip to content

Commit 9ec3bb5

Browse files
authored
feat: follow supabase-ex release (#14)
1 parent e893618 commit 9ec3bb5

22 files changed

+479
-778
lines changed

.dialyzerignore

Whitespace-only changes.

.github/workflows/ci.yml

+128-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ jobs:
1212
lint:
1313
runs-on: ubuntu-latest
1414

15+
env:
16+
MIX_ENV: test
17+
1518
strategy:
1619
matrix:
1720
elixir: [1.18.1]
18-
otp: [27.2]
21+
otp: [27.0]
1922

2023
steps:
2124
- name: Checkout code
@@ -60,3 +63,127 @@ jobs:
6063

6164
- name: Run Credo
6265
run: mix credo --strict
66+
67+
static-analisys:
68+
runs-on: ubuntu-latest
69+
70+
env:
71+
MIX_ENV: test
72+
73+
strategy:
74+
matrix:
75+
elixir: [1.18.1]
76+
otp: [27.0]
77+
78+
steps:
79+
- name: Checkout code
80+
uses: actions/checkout@v3
81+
82+
- name: Set up Elixir
83+
uses: erlef/setup-beam@v1
84+
with:
85+
elixir-version: ${{ matrix.elixir }}
86+
otp-version: ${{ matrix.otp }}
87+
88+
- name: Cache Elixir deps
89+
uses: actions/cache@v1
90+
id: deps-cache
91+
with:
92+
path: deps
93+
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
94+
95+
- name: Cache Elixir _build
96+
uses: actions/cache@v1
97+
id: build-cache
98+
with:
99+
path: _build
100+
key: ${{ runner.os }}-build-${{ env.MIX_ENV }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
101+
102+
- name: Install deps
103+
if: steps.deps-cache.outputs.cache-hit != 'true'
104+
run: |
105+
mix local.rebar --force
106+
mix local.hex --force
107+
mix deps.get --only ${{ env.MIX_ENV }}
108+
109+
- name: Compile deps
110+
if: steps.build-cache.outputs.cache-hit != 'true'
111+
run: mix deps.compile --warnings-as-errors
112+
113+
# Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones
114+
# Cache key based on Elixir & Erlang version (also useful when running in matrix)
115+
- name: Restore PLT cache
116+
uses: actions/cache/restore@v3
117+
id: plt_cache
118+
with:
119+
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
120+
restore-keys: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
121+
path: priv/plts
122+
123+
# Create PLTs if no cache was found
124+
- name: Create PLTs
125+
if: steps.plt_cache.outputs.cache-hit != 'true'
126+
run: mix dialyzer --plt
127+
128+
- name: Save PLT cache
129+
uses: actions/cache/save@v3
130+
if: steps.plt_cache.outputs.cache-hit != 'true'
131+
id: plt_cache_save
132+
with:
133+
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
134+
path: priv/plts
135+
136+
- name: Run dialyzer
137+
run: mix dialyzer --format github
138+
139+
test:
140+
runs-on: ubuntu-latest
141+
142+
env:
143+
MIX_ENV: test
144+
145+
strategy:
146+
matrix:
147+
elixir: [1.18.1]
148+
otp: [27.0]
149+
150+
steps:
151+
- name: Checkout code
152+
uses: actions/checkout@v3
153+
154+
- name: Set up Elixir
155+
uses: erlef/setup-beam@v1
156+
with:
157+
elixir-version: ${{ matrix.elixir }}
158+
otp-version: ${{ matrix.otp }}
159+
160+
- name: Cache Elixir deps
161+
uses: actions/cache@v1
162+
id: deps-cache
163+
with:
164+
path: deps
165+
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
166+
167+
- name: Cache Elixir _build
168+
uses: actions/cache@v1
169+
id: build-cache
170+
with:
171+
path: _build
172+
key: ${{ runner.os }}-build-${{ env.MIX_ENV }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
173+
174+
- name: Install deps
175+
if: steps.deps-cache.outputs.cache-hit != 'true'
176+
run: |
177+
mix local.rebar --force
178+
mix local.hex --force
179+
mix deps.get --only ${{ env.MIX_ENV }}
180+
181+
- name: Compile deps
182+
if: steps.build-cache.outputs.cache-hit != 'true'
183+
run: mix deps.compile --warnings-as-errors
184+
185+
- name: Clean build
186+
run: mix clean
187+
188+
- name: Run tests
189+
run: mix test

.wakatime-project

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
postgrest-ex

lib/supabase/postgrest.ex

+29-91
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Supabase.PostgREST do
1111

1212
alias Supabase.Client
1313
alias Supabase.Fetcher
14-
alias Supabase.PostgREST.Builder
14+
alias Supabase.Fetcher.Request
1515
alias Supabase.PostgREST.Error
1616

1717
alias Supabase.PostgREST.FilterBuilder
@@ -78,7 +78,8 @@ defmodule Supabase.PostgREST do
7878
@impl true
7979
def from(%Client{} = client, table) do
8080
client
81-
|> Builder.new(relation: table)
81+
|> Request.new()
82+
|> Request.with_database_url(table)
8283
|> with_custom_media_type(:default)
8384
end
8485

@@ -94,8 +95,8 @@ defmodule Supabase.PostgREST do
9495
iex> Supabase.PostgREST.schema(builder, private)
9596
"""
9697
@impl true
97-
def schema(%Builder{} = b, schema) when is_binary(schema) do
98-
%{b | schema: schema}
98+
def schema(%Request{} = b, schema) when is_binary(schema) do
99+
put_in(b.client.db.schema, schema)
99100
end
100101

101102
@doc """
@@ -112,10 +113,10 @@ defmodule Supabase.PostgREST do
112113
- [PostgREST resource represation docs](https://docs.postgrest.org/en/v12/references/api/resource_representation.html)
113114
"""
114115
@impl true
115-
def with_custom_media_type(%Builder{} = b, media_type)
116+
def with_custom_media_type(%Request{} = b, media_type)
116117
when is_atom(media_type) do
117118
header = @accept_headers[media_type] || @accept_headers[:default]
118-
Builder.add_request_header(b, "accept", header)
119+
Request.with_headers(b, %{"accept" => header})
119120
end
120121

121122
@doc """
@@ -131,26 +132,7 @@ defmodule Supabase.PostgREST do
131132
- Supabase query execution: https://supabase.com/docs/reference/javascript/performing-queries
132133
"""
133134
@impl true
134-
def execute(%Builder{} = b), do: do_execute(b)
135-
136-
@doc """
137-
Executes the query and returns the result as a JSON-encoded string.
138-
139-
## Parameters
140-
- `builder`: The Builder or Builder instance to execute.
141-
142-
## Examples
143-
iex> PostgREST.execute_string(builder)
144-
145-
## See also
146-
- Supabase query execution and response handling: https://supabase.com/docs/reference/javascript/performing-queries
147-
"""
148-
@impl true
149-
def execute_string(%Builder{} = b) do
150-
with {:ok, body} <- do_execute(b) do
151-
Jason.encode(body)
152-
end
153-
end
135+
def execute(%Request{} = b), do: do_execute(b)
154136

155137
@doc """
156138
Executes the query and maps the resulting data to a specified schema struct, useful for casting the results to Elixir structs.
@@ -166,88 +148,44 @@ defmodule Supabase.PostgREST do
166148
- Supabase query execution and schema casting: https://supabase.com/docs/reference/javascript/performing-queries
167149
"""
168150
@impl true
169-
def execute_to(%Builder{} = b, schema) when is_atom(schema) do
170-
with {:ok, body} <- do_execute(b) do
171-
if is_list(body) do
172-
{:ok, Enum.map(body, &struct(schema, &1))}
173-
else
174-
{:ok, struct(schema, body)}
175-
end
176-
end
151+
def execute_to(%Request{} = b, schema) when is_atom(schema) do
152+
alias Supabase.PostgREST.SchemaDecoder
153+
154+
Request.with_body_decoder(b, SchemaDecoder, schema: schema)
155+
|> do_execute()
177156
end
178157

179158
@doc """
180-
Executes a query using the Finch HTTP client, formatting the request appropriately. Returns the HTTP request without executing it.
159+
Builds a query using the Finch HTTP client, formatting the request appropriately. Returns the HTTP request without executing it.
181160
182161
## Parameters
183162
- `builder`: The Builder or Builder instance to execute.
184-
- `schema`: Optional schema module to map the results.
185163
186164
## Examples
187-
iex> PostgREST.execute_to_finch_request(builder, User)
165+
iex> PostgREST.execute_to_finch_request(builder)
188166
189167
## See also
190168
- Supabase query execution: https://supabase.com/docs/reference/javascript/performing-queries
191169
"""
192170
@impl true
193-
def execute_to_finch_request(%Builder{client: client} = b) do
194-
headers = Fetcher.apply_client_headers(client, nil, Map.to_list(b.headers))
195-
query = URI.encode_query(b.params)
196-
url = URI.new!(b.url) |> URI.append_query(query)
171+
def execute_to_finch_request(%Request{} = b) do
172+
query = URI.encode_query(b.query)
173+
url = URI.parse(b.url) |> URI.append_query(query)
197174

198-
Supabase.Fetcher.new_connection(b.method, url, b.body, headers)
175+
Finch.build(b.method, url, b.headers, b.body)
199176
end
200177

201-
defp do_execute(%Builder{client: client} = b) do
202-
headers = Fetcher.apply_client_headers(client, nil, Map.to_list(b.headers))
203-
query = URI.encode_query(b.params)
204-
url = URI.new!(b.url) |> URI.append_query(query)
205-
request = request_fun_from_method(b.method)
178+
defp do_execute(%Request{client: client} = b) do
179+
schema = client.db.schema
206180

207-
url
208-
|> request.(b.body, headers)
209-
|> parse_response()
210-
end
211-
212-
defp request_fun_from_method(:get), do: &Supabase.Fetcher.get/3
213-
defp request_fun_from_method(:head), do: &Supabase.Fetcher.head/3
214-
defp request_fun_from_method(:post), do: &Supabase.Fetcher.post/3
215-
defp request_fun_from_method(:delete), do: &Supabase.Fetcher.delete/3
216-
defp request_fun_from_method(:patch), do: &Supabase.Fetcher.patch/3
217-
218-
defp parse_response({:error, reason}), do: {:error, reason}
219-
220-
defp parse_response({:ok, %{status: _, body: ""}}) do
221-
{:ok, nil}
222-
end
223-
224-
defp parse_response({:ok, %{status: status, body: raw, headers: headers}}) do
225-
if json_content?(headers) do
226-
with {:ok, body} <- Jason.decode(raw, keys: :atoms) do
227-
cond do
228-
error_resp?(status) -> {:error, Error.from_raw_body(body)}
229-
success_resp?(status) -> {:ok, body}
230-
end
231-
end
232-
else
233-
{:ok, raw}
234-
end
235-
end
236-
237-
defp json_content?(headers) when is_list(headers) do
238-
headers
239-
|> Enum.find_value(fn
240-
{"content-type", type} -> type
241-
_ -> false
242-
end)
243-
|> String.match?(~r/json/)
244-
end
245-
246-
defp error_resp?(status) do
247-
Kernel.in(status, 400..599)
248-
end
181+
schema_header =
182+
if b.method in [:get, :head],
183+
do: %{"accept-profile" => schema},
184+
else: %{"content-profile" => schema}
249185

250-
defp success_resp?(status) do
251-
Kernel.in(status, 200..399)
186+
b
187+
|> Request.with_error_parser(Error)
188+
|> Request.with_headers(schema_header)
189+
|> Fetcher.request()
252190
end
253191
end

lib/supabase/postgrest/behaviour.ex

+7-13
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,16 @@ defmodule Supabase.PostgREST.Behaviour do
22
@moduledoc "Defines the interface for the main module Supabase.PostgREST"
33

44
alias Supabase.Client
5-
alias Supabase.PostgREST.Builder
6-
alias Supabase.PostgREST.Error
5+
alias Supabase.Fetcher.Request
76

87
@type media_type ::
98
:json | :csv | :openapi | :geojson | :pgrst_plan | :pgrst_object | :pgrst_array
109

11-
@callback with_custom_media_type(builder, media_type) :: builder
12-
when builder: Builder.t() | Builder.t()
13-
@callback from(Client.t(), relation :: String.t()) :: Builder.t()
14-
@callback schema(Builder.t(), schema :: String.t()) :: Builder.t()
10+
@callback with_custom_media_type(Request.t(), media_type) :: Request.t()
11+
@callback from(Client.t(), relation :: String.t()) :: Request.t()
12+
@callback schema(Request.t(), schema :: String.t()) :: Request.t()
1513

16-
@callback execute(Builder.t() | Builder.t()) :: {:ok, term} | {:error, Error.t()}
17-
@callback execute_string(Builder.t() | Builder.t()) ::
18-
{:ok, binary} | {:error, Error.t() | atom}
19-
@callback execute_to(Builder.t() | Builder.t(), atom) ::
20-
{:ok, term} | {:error, Error.t() | atom}
21-
@callback execute_to_finch_request(Builder.t() | Builder.t()) ::
22-
Finch.Request.t()
14+
@callback execute(Request.t()) :: Supabase.result(term)
15+
@callback execute_to(Request.t(), module) :: Supabase.result(term)
16+
@callback execute_to_finch_request(Request.t()) :: Finch.Request.t()
2317
end

0 commit comments

Comments
 (0)